summaryrefslogtreecommitdiffstats
path: root/third_party/heimdal/kdc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/heimdal/kdc')
-rw-r--r--third_party/heimdal/kdc/Makefile.am263
-rw-r--r--third_party/heimdal/kdc/NTMakefile167
-rw-r--r--third_party/heimdal/kdc/altsecid_gss_preauth_authorizer.c541
-rw-r--r--third_party/heimdal/kdc/announce.c544
-rw-r--r--third_party/heimdal/kdc/bx509d.8480
-rw-r--r--third_party/heimdal/kdc/bx509d.c3175
-rw-r--r--third_party/heimdal/kdc/ca.c133
-rw-r--r--third_party/heimdal/kdc/cjwt_token_validator.c343
-rw-r--r--third_party/heimdal/kdc/config.c329
-rw-r--r--third_party/heimdal/kdc/connect.c1319
-rw-r--r--third_party/heimdal/kdc/csr_authorizer.c91
-rw-r--r--third_party/heimdal/kdc/csr_authorizer_plugin.h74
-rw-r--r--third_party/heimdal/kdc/default_config.c464
-rw-r--r--third_party/heimdal/kdc/digest-service.c291
-rw-r--r--third_party/heimdal/kdc/digest.c1520
-rw-r--r--third_party/heimdal/kdc/fast.c1002
-rw-r--r--third_party/heimdal/kdc/gss_preauth.c1034
-rw-r--r--third_party/heimdal/kdc/gss_preauth_authorizer_plugin.h75
-rw-r--r--third_party/heimdal/kdc/headers.h118
-rw-r--r--third_party/heimdal/kdc/hprop-version.rc36
-rw-r--r--third_party/heimdal/kdc/hprop.8130
-rw-r--r--third_party/heimdal/kdc/hprop.c482
-rw-r--r--third_party/heimdal/kdc/hprop.h59
-rw-r--r--third_party/heimdal/kdc/hpropd-version.rc36
-rw-r--r--third_party/heimdal/kdc/hpropd.887
-rw-r--r--third_party/heimdal/kdc/hpropd.c285
-rw-r--r--third_party/heimdal/kdc/httpkadmind.8653
-rw-r--r--third_party/heimdal/kdc/httpkadmind.c2762
-rw-r--r--third_party/heimdal/kdc/ipc_csr_authorizer.c681
-rw-r--r--third_party/heimdal/kdc/kdc-accessors.h394
-rw-r--r--third_party/heimdal/kdc/kdc-audit.h72
-rw-r--r--third_party/heimdal/kdc/kdc-plugin.c753
-rw-r--r--third_party/heimdal/kdc/kdc-plugin.h151
-rw-r--r--third_party/heimdal/kdc/kdc-replay.c214
-rw-r--r--third_party/heimdal/kdc/kdc-tester.c517
-rw-r--r--third_party/heimdal/kdc/kdc-version.rc36
-rw-r--r--third_party/heimdal/kdc/kdc.8217
-rw-r--r--third_party/heimdal/kdc/kdc.h138
-rw-r--r--third_party/heimdal/kdc/kdc_locl.h260
-rw-r--r--third_party/heimdal/kdc/kerberos5.c3247
-rw-r--r--third_party/heimdal/kdc/krb5tgs.c2321
-rw-r--r--third_party/heimdal/kdc/kstash-version.rc36
-rw-r--r--third_party/heimdal/kdc/kstash.892
-rw-r--r--third_party/heimdal/kdc/kstash.c189
-rw-r--r--third_party/heimdal/kdc/kx509.c1080
-rw-r--r--third_party/heimdal/kdc/libkdc-exports.def107
-rw-r--r--third_party/heimdal/kdc/libkdc-version.rc36
-rw-r--r--third_party/heimdal/kdc/log.c110
-rw-r--r--third_party/heimdal/kdc/main.c181
-rw-r--r--third_party/heimdal/kdc/misc.c363
-rw-r--r--third_party/heimdal/kdc/mit_dump.c227
-rw-r--r--third_party/heimdal/kdc/mssfu.c693
-rw-r--r--third_party/heimdal/kdc/negotiate_token_validator.c323
-rw-r--r--third_party/heimdal/kdc/pkinit-ec.c548
-rw-r--r--third_party/heimdal/kdc/pkinit.c2223
-rw-r--r--third_party/heimdal/kdc/process.c611
-rw-r--r--third_party/heimdal/kdc/set_dbinfo.c113
-rw-r--r--third_party/heimdal/kdc/string2key-version.rc36
-rw-r--r--third_party/heimdal/kdc/string2key.885
-rw-r--r--third_party/heimdal/kdc/string2key.c189
-rw-r--r--third_party/heimdal/kdc/test_csr_authorizer.c597
-rw-r--r--third_party/heimdal/kdc/test_kdc_ca.c178
-rw-r--r--third_party/heimdal/kdc/test_token_validator.c121
-rw-r--r--third_party/heimdal/kdc/token_validator.c122
-rw-r--r--third_party/heimdal/kdc/token_validator_plugin.h83
-rw-r--r--third_party/heimdal/kdc/version-script.map113
66 files changed, 33950 insertions, 0 deletions
diff --git a/third_party/heimdal/kdc/Makefile.am b/third_party/heimdal/kdc/Makefile.am
new file mode 100644
index 0000000..ca58359
--- /dev/null
+++ b/third_party/heimdal/kdc/Makefile.am
@@ -0,0 +1,263 @@
+# $Id$
+
+include $(top_srcdir)/Makefile.am.common
+
+WFLAGS += $(WFLAGS_ENUM_CONV)
+
+AM_CPPFLAGS += $(INCLUDE_libintl) $(INCLUDE_openssl_crypto) -I$(srcdir)/../lib/krb5
+
+lib_LTLIBRARIES = ipc_csr_authorizer.la \
+ negotiate_token_validator.la \
+ libkdc.la
+
+if HAVE_CJWT
+lib_LTLIBRARIES += cjwt_token_validator.la
+endif
+if OPENLDAP
+lib_LTLIBRARIES += altsecid_gss_preauth_authorizer.la
+endif
+
+
+bin_PROGRAMS = string2key
+
+sbin_PROGRAMS = kstash
+
+libexec_PROGRAMS = hprop hpropd kdc digest-service \
+ test_token_validator test_csr_authorizer test_kdc_ca
+
+noinst_PROGRAMS = kdc-replay kdc-tester
+
+man_MANS = bx509d.8 httpkadmind.8 kdc.8 kstash.8 hprop.8 hpropd.8 string2key.8
+
+hprop_SOURCES = hprop.c mit_dump.c hprop.h
+hpropd_SOURCES = hpropd.c hprop.h
+
+kstash_SOURCES = kstash.c headers.h
+
+string2key_SOURCES = string2key.c headers.h
+
+if HAVE_MICROHTTPD
+bx509d_SOURCES = bx509d.c
+bx509d_AM_CPPFLAGS = $(AM_CPPFLAGS) $(MICROHTTPD_CFLAGS)
+bx509d_LDADD = -ldl \
+ $(top_builddir)/lib/hdb/libhdb.la \
+ libkdc.la \
+ $(MICROHTTPD_LIBS) \
+ $(LIB_roken) \
+ $(LIB_heimbase) \
+ $(LIB_hcrypto) \
+ $(top_builddir)/lib/sl/libsl.la \
+ $(top_builddir)/lib/asn1/libasn1.la \
+ $(top_builddir)/lib/krb5/libkrb5.la \
+ $(top_builddir)/lib/hx509/libhx509.la \
+ $(top_builddir)/lib/gssapi/libgssapi.la
+libexec_PROGRAMS += bx509d
+
+httpkadmind_SOURCES = httpkadmind.c
+httpkadmind_AM_CPPFLAGS = $(AM_CPPFLAGS) $(MICROHTTPD_CFLAGS)
+httpkadmind_LDADD = -ldl \
+ $(top_builddir)/lib/hdb/libhdb.la \
+ $(top_builddir)/lib/kadm5/libkadm5clnt.la \
+ $(top_builddir)/lib/kadm5/libkadm5srv.la \
+ libkdc.la \
+ $(MICROHTTPD_LIBS) \
+ $(LIB_roken) \
+ $(LIB_heimbase) \
+ $(LIB_hcrypto) \
+ $(top_builddir)/lib/sl/libsl.la \
+ $(top_builddir)/lib/asn1/libasn1.la \
+ $(top_builddir)/lib/krb5/libkrb5.la \
+ $(top_builddir)/lib/hx509/libhx509.la \
+ $(top_builddir)/lib/gssapi/libgssapi.la
+libexec_PROGRAMS += httpkadmind
+endif
+
+digest_service_SOURCES = \
+ digest-service.c
+
+kdc_SOURCES = connect.c \
+ config.c \
+ announce.c \
+ main.c
+
+kdc_tester_SOURCES = \
+ config.c \
+ kdc-tester.c
+
+test_token_validator_SOURCES = test_token_validator.c
+test_csr_authorizer_SOURCES = test_csr_authorizer.c
+test_kdc_ca_SOURCES = test_kdc_ca.c
+
+# Token plugins (for bx509d)
+if HAVE_CJWT
+cjwt_token_validator_la_SOURCES = cjwt_token_validator.c
+cjwt_token_validator_la_CFLAGS = $(CJSON_CFLAGS) $(CJWT_CFLAGS)
+cjwt_token_validator_la_LDFLAGS = -module $(CJSON_LIBS) $(CJWT_LIBS)
+endif
+
+negotiate_token_validator_la_SOURCES = negotiate_token_validator.c
+negotiate_token_validator_la_LDFLAGS = -module $(LIB_gssapi)
+# CSR Authorizer plugins (for kdc/kx509 and bx509d)
+ipc_csr_authorizer_la_SOURCES = ipc_csr_authorizer.c
+ipc_csr_authorizer_la_LDFLAGS = -module \
+ $(top_builddir)/lib/krb5/libkrb5.la \
+ $(top_builddir)/lib/hx509/libhx509.la \
+ $(top_builddir)/lib/ipc/libheim-ipcc.la \
+ $(top_builddir)/lib/roken/libroken.la
+
+# GSS-API authorization plugins
+if OPENLDAP
+altsecid_gss_preauth_authorizer_la_SOURCES = altsecid_gss_preauth_authorizer.c
+altsecid_gss_preauth_authorizer_la_LDFLAGS = -module \
+ $(top_builddir)/lib/gssapi/libgssapi.la \
+ $(top_builddir)/lib/krb5/libkrb5.la \
+ $(LIB_openldap)
+endif
+
+libkdc_la_CPPFLAGS = -DBUILD_KDC_LIB $(AM_CPPFLAGS)
+
+libkdc_la_SOURCES = \
+ default_config.c \
+ ca.c \
+ set_dbinfo.c \
+ digest.c \
+ fast.c \
+ kdc_locl.h \
+ kerberos5.c \
+ krb5tgs.c \
+ pkinit.c \
+ pkinit-ec.c \
+ mssfu.c \
+ log.c \
+ misc.c \
+ kx509.c \
+ token_validator.c \
+ csr_authorizer.c \
+ process.c \
+ kdc-plugin.c \
+ gss_preauth.c
+
+KDC_PROTOS = $(srcdir)/kdc-protos.h $(srcdir)/kdc-private.h
+
+ALL_OBJECTS = $(kdc_OBJECTS)
+ALL_OBJECTS += $(kdc_replay_OBJECTS)
+ALL_OBJECTS += $(kdc_tester_OBJECTS)
+ALL_OBJECTS += $(test_token_validator_OBJECTS)
+ALL_OBJECTS += $(test_csr_authorizer_OBJECTS)
+ALL_OBJECTS += $(test_kdc_ca_OBJECTS)
+ALL_OBJECTS += $(libkdc_la_OBJECTS)
+ALL_OBJECTS += $(string2key_OBJECTS)
+ALL_OBJECTS += $(kstash_OBJECTS)
+ALL_OBJECTS += $(hprop_OBJECTS)
+ALL_OBJECTS += $(hpropd_OBJECTS)
+ALL_OBJECTS += $(digest_service_OBJECTS)
+ALL_OBJECTS += $(bx509d_OBJECTS)
+ALL_OBJECTS += $(httpkadmind_OBJECTS)
+ALL_OBJECTS += $(cjwt_token_validator_la_OBJECTS)
+ALL_OBJECTS += $(test_token_validator_OBJECTS)
+ALL_OBJECTS += $(test_csr_authorizer_OBJECTS)
+ALL_OBJECTS += $(test_kdc_ca_OBJECTS)
+ALL_OBJECTS += $(ipc_csr_authorizer_la_OBJECTS)
+ALL_OBJECTS += $(negotiate_token_validator_la_OBJECTS)
+
+$(ALL_OBJECTS): $(KDC_PROTOS)
+
+libkdc_la_LDFLAGS = -version-info 2:0:0
+
+if versionscript
+libkdc_la_LDFLAGS += $(LDFLAGS_VERSION_SCRIPT)$(srcdir)/version-script.map
+endif
+
+$(libkdc_la_OBJECTS): $(srcdir)/version-script.map
+
+$(srcdir)/kdc-protos.h: $(libkdc_la_SOURCES)
+ cd $(srcdir) && perl ../cf/make-proto.pl -E KDC_LIB -q -P comment -o kdc-protos.h $(libkdc_la_SOURCES) || rm -f kdc-protos.h
+
+$(srcdir)/kdc-private.h: $(libkdc_la_SOURCES)
+ cd $(srcdir) && perl ../cf/make-proto.pl -q -P comment -p kdc-private.h $(libkdc_la_SOURCES) || rm -f kdc-private.h
+
+
+hprop_LDADD = \
+ $(top_builddir)/lib/hdb/libhdb.la \
+ $(top_builddir)/lib/krb5/libkrb5.la \
+ $(LIB_kdb) \
+ $(LIB_hcrypto) \
+ $(top_builddir)/lib/asn1/libasn1.la \
+ $(LIB_roken) \
+ $(DB3LIB) $(DB1LIB) $(LMDBLIB) $(NDBMLIB)
+
+hpropd_LDADD = \
+ $(top_builddir)/lib/hdb/libhdb.la \
+ $(top_builddir)/lib/krb5/libkrb5.la \
+ $(LIB_kdb) \
+ $(LIB_hcrypto) \
+ $(top_builddir)/lib/asn1/libasn1.la \
+ $(LIB_roken) \
+ $(DB3LIB) $(DB1LIB) $(LMDBLIB) $(NDBMLIB)
+
+if PKINIT
+LIB_pkinit = $(top_builddir)/lib/hx509/libhx509.la
+endif
+
+libkdc_la_LIBADD = \
+ $(LIB_pkinit) \
+ $(top_builddir)/lib/hdb/libhdb.la \
+ $(top_builddir)/lib/krb5/libkrb5.la \
+ $(top_builddir)/lib/gssapi/libgssapi.la \
+ $(top_builddir)/lib/gss_preauth/libgss_preauth.la \
+ $(LIB_kdb) \
+ $(top_builddir)/lib/ntlm/libheimntlm.la \
+ $(LIB_hcrypto) \
+ $(LIB_openssl_crypto) \
+ $(top_builddir)/lib/asn1/libasn1.la \
+ $(LIB_roken) \
+ $(DB3LIB) $(DB1LIB) $(LMDBLIB) $(NDBMLIB)
+
+LDADD = $(top_builddir)/lib/hdb/libhdb.la \
+ $(top_builddir)/lib/krb5/libkrb5.la \
+ $(LIB_hcrypto) \
+ $(top_builddir)/lib/asn1/libasn1.la \
+ $(LIB_roken) \
+ $(DB3LIB) $(DB1LIB) $(LMDBLIB) $(NDBMLIB)
+
+kdc_LDADD = libkdc.la $(LDADD) $(LIB_pidfile) $(CAPNG_LIBS)
+
+if FRAMEWORK_SECURITY
+kdc_LDFLAGS = -framework SystemConfiguration -framework CoreFoundation
+endif
+kdc_CFLAGS = $(CAPNG_CFLAGS)
+
+digest_service_LDADD = \
+ libkdc.la \
+ $(top_builddir)/lib/ntlm/libheimntlm.la \
+ $(top_builddir)/lib/ipc/libheim-ipcs.la \
+ $(LDADD) $(LIB_pidfile)
+kdc_replay_LDADD = libkdc.la $(LDADD) $(LIB_pidfile)
+kdc_tester_LDADD = libkdc.la $(LDADD) $(LIB_pidfile) $(LIB_heimbase)
+test_token_validator_LDADD = libkdc.la $(LDADD) $(LIB_pidfile) $(LIB_heimbase) $(LIB_gssapi)
+test_csr_authorizer_LDADD = libkdc.la \
+ $(top_builddir)/lib/hx509/libhx509.la \
+ $(LDADD) \
+ $(LIB_pidfile) \
+ $(LIB_heimbase) \
+ $(top_builddir)/lib/ipc/libheim-ipcs.la
+test_kdc_ca_LDADD = libkdc.la $(top_builddir)/lib/hx509/libhx509.la $(LDADD) $(LIB_pidfile) $(LIB_heimbase)
+
+include_HEADERS = kdc.h $(srcdir)/kdc-protos.h
+
+noinst_HEADERS = $(srcdir)/kdc-private.h
+
+krb5dir = $(includedir)/krb5
+krb5_HEADERS = kdc-audit.h kdc-plugin.h kdc-accessors.h token_validator_plugin.h csr_authorizer_plugin.h gss_preauth_authorizer_plugin.h
+
+build_HEADERZ = $(krb5_HEADERS) # XXX
+
+EXTRA_DIST = \
+ hprop-version.rc \
+ hpropd-version.rc \
+ kdc-version.rc \
+ kstash-version.rc \
+ libkdc-version.rc \
+ string2key-version.rc \
+ libkdc-exports.def \
+ NTMakefile $(man_MANS) version-script.map
diff --git a/third_party/heimdal/kdc/NTMakefile b/third_party/heimdal/kdc/NTMakefile
new file mode 100644
index 0000000..aca65b1
--- /dev/null
+++ b/third_party/heimdal/kdc/NTMakefile
@@ -0,0 +1,167 @@
+########################################################################
+#
+# Copyright (c) 2009-2016, Secure Endpoints Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# - Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# - Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+RELDIR=kdc
+
+!include ../windows/NTMakefile.w32
+
+intcflags=-I$(OBJ) -I$(SRC)\lib\gssapi -I$(OBJDIR)\lib\gssapi -I$(OBJDIR)\lib\gss_preauth -DBUILD_KDC_LIB
+
+BINPROGRAMS=$(BINDIR)\string2key.exe
+
+SBINPROGRAMS=$(SBINDIR)\kstash.exe
+
+LIBEXECPROGRAMS= \
+ $(LIBEXECDIR)\hprop.exe \
+ $(LIBEXECDIR)\hpropd.exe \
+ $(LIBEXECDIR)\kdc.exe \
+# $(LIBEXECDIR)\digest-service.exe
+
+NOINST_PROGRAMS=$(OBJ)\kdc-replay.exe
+
+INCFILES=\
+ $(INCDIR)\kdc.h \
+ $(INCDIR)\kdc-protos.h \
+ $(INCDIR)\kdc-private.h \
+ $(INCDIR)\krb5\kdc-audit.h \
+ $(INCDIR)\krb5\kdc-plugin.h \
+ $(INCDIR)\krb5\kdc-accessors.h
+
+all:: $(INCFILES) $(LIBKDC) $(BINPROGRAMS) $(SBINPROGRAMS) $(LIBEXECPROGRAMS)
+
+clean::
+ -$(RM) $(LIBKDC)
+ -$(RM) $(BINPROGRAMS:.exe=.*) $(SBINPROGRAMS:.exe=.*) $(LIBEXECPROGRAMS:.exe=.*)
+
+BIN_LIBS=\
+ $(LIBHDB) \
+ $(LIBHEIMDAL) \
+ $(LIBROKEN) \
+ $(LIBVERS)
+
+$(LIBEXECDIR)\hprop.exe: $(OBJ)\hprop.obj $(OBJ)\mit_dump.obj $(BIN_LIBS) $(OBJ)\hprop-version.res
+ $(EXECONLINK)
+ $(EXEPREP)
+
+$(LIBEXECDIR)\hpropd.exe: $(OBJ)\hpropd.obj $(BIN_LIBS) $(OBJ)\hpropd-version.res
+ $(EXECONLINK)
+ $(EXEPREP)
+
+$(SBINDIR)\kstash.exe: $(OBJ)\kstash.obj $(BIN_LIBS) $(OBJ)\kstash-version.res
+ $(EXECONLINK)
+ $(EXEPREP)
+
+$(BINDIR)\string2key.exe: $(OBJ)\string2key.obj $(BIN_LIBS) $(OBJ)\string2key-version.res
+ $(EXECONLINK)
+ $(EXEPREP)
+
+$(BINDIR)\digest-service.exe: $(OBJ)\digest-service.obj $(BIN_LIBS)
+ $(EXECONLINK)
+ $(EXEPREP)
+
+$(LIBEXECDIR)\kdc.exe: \
+ $(OBJ)\connect.obj $(OBJ)\config.obj $(OBJ)\announce.obj \
+ $(OBJ)\main.obj $(OBJ)\kdc-version.res \
+ $(LIBKDC) $(BIN_LIBS) $(LIB_openssl_crypto)
+ $(EXECONLINK)
+ $(EXEPREP)
+
+LIBKDC_OBJS=\
+ $(OBJ)\default_config.obj \
+ $(OBJ)\ca.obj \
+ $(OBJ)\kx509.obj \
+ $(OBJ)\set_dbinfo.obj \
+ $(OBJ)\digest.obj \
+ $(OBJ)\fast.obj \
+ $(OBJ)\kerberos5.obj \
+ $(OBJ)\krb5tgs.obj \
+ $(OBJ)\pkinit.obj \
+ $(OBJ)\pkinit-ec.obj \
+ $(OBJ)\mssfu.obj \
+ $(OBJ)\log.obj \
+ $(OBJ)\misc.obj \
+ $(OBJ)\kx509.obj \
+ $(OBJ)\token_validator.obj \
+ $(OBJ)\csr_authorizer.obj \
+ $(OBJ)\process.obj \
+ $(OBJ)\kdc-plugin.obj \
+ $(OBJ)\gss_preauth.obj
+
+LIBKDC_LIBS=\
+ $(LIBHDB) \
+ $(LIBGSS_PREAUTH) \
+ $(LIBGSSAPI) \
+ $(LIBHEIMBASE) \
+ $(LIBHEIMDAL) \
+ $(LIBHEIMNTLM) \
+ $(LIB_openssl_crypto) \
+ $(LIBROKEN)
+
+LIBKDCRES=$(OBJ)\libkdc-version.res
+
+$(LIBEXECDIR)\libkdc.dll: $(LIBKDC_OBJS) $(LIBKDC_LIBS) $(LIBKDCRES)
+ $(DLLGUILINK) -implib:$(LIBKDC) -def:libkdc-exports.def
+ $(DLLPREP_NODIST)
+
+$(LIBKDC): $(LIBEXECDIR)\libkdc.dll
+
+clean::
+ -$(RM) $(LIBEXECDIR)\libkdc.*
+
+libkdc_la_SOURCES = \
+ default_config.c \
+ ca.c \
+ set_dbinfo.c \
+ digest.c \
+ fast.c \
+ kdc_locl.h \
+ kerberos5.c \
+ krb5tgs.c \
+ pkinit.c \
+ pkinit-ec.c \
+ mssfu.c \
+ log.c \
+ misc.c \
+ kx509.c \
+ token_validator.c \
+ csr_authorizer.c \
+ process.c \
+ kdc-plugin.c \
+ gss_preauth.c
+
+$(OBJ)\kdc-protos.h: $(libkdc_la_SOURCES)
+ cd $(SRCDIR)
+ $(PERL) ..\cf\make-proto.pl -E KDC_LIB -q -P remove -o $@ $(libkdc_la_SOURCES) \
+ || $(RM) $@
+
+$(OBJ)\kdc-private.h: $(libkdc_la_SOURCES)
+ $(PERL) ..\cf\make-proto.pl -q -P remove -p $@ $(libkdc_la_SOURCES) \
+ || $(RM) $@
diff --git a/third_party/heimdal/kdc/altsecid_gss_preauth_authorizer.c b/third_party/heimdal/kdc/altsecid_gss_preauth_authorizer.c
new file mode 100644
index 0000000..d48ea58
--- /dev/null
+++ b/third_party/heimdal/kdc/altsecid_gss_preauth_authorizer.c
@@ -0,0 +1,541 @@
+/*
+ * Copyright (c) 2021, PADL Software Pty Ltd.
+ * All rights reserved.
+ *
+ * Portions Copyright (c) 2004 Kungliga Tekniska Högskolan
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of PADL Software nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY PADL SOFTWARE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL PADL SOFTWARE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This plugin authorizes federated GSS-API pre-authentication clients by
+ * querying an AD DC in the KDC realm for the altSecurityIdentities
+ * attribute.
+ *
+ * For example, GSS-API initiator foo@AAA.H5L.SE using the eap-aes128
+ * mechanism to authenticate in realm H5L.SE would require a user entry
+ * where altSecurityIdentities equals either:
+ *
+ * EAP:foo@AAA.H5L.SE
+ * EAP-AES128:foo@AAA.H5L.SE
+ *
+ * (Stripping the mechanism name after the hyphen is a convention
+ * intended to allow mechanism variants to be grouped together.)
+ *
+ * Once the user entry is found, the name is canonicalized by reading the
+ * sAMAccountName attribute and concatenating it with the KDC realm,
+ * specifically the canonicalized realm of the WELLKNOWN/FEDERATED HDB
+ * entry.
+ *
+ * The KDC will need to have access to a default credentials cache, or
+ * OpenLDAP will need to be linked against a version of Cyrus SASL and
+ * Heimdal that supports keytab credentials.
+ */
+
+#include "kdc_locl.h"
+
+#include <resolve.h>
+#include <common_plugin.h>
+#include <heimqueue.h>
+
+#include <gssapi/gssapi.h>
+#include <gssapi_mech.h>
+
+#include <ldap.h>
+
+#include "gss_preauth_authorizer_plugin.h"
+
+#ifndef PAC_REQUESTOR_SID
+#define PAC_REQUESTOR_SID 18
+#endif
+
+struct ad_server_tuple {
+ HEIM_TAILQ_ENTRY(ad_server_tuple) link;
+ char *realm;
+ LDAP *ld;
+#ifdef LDAP_OPT_X_SASL_GSS_CREDS
+ gss_cred_id_t gss_cred;
+#endif
+};
+
+struct altsecid_gss_preauth_authorizer_context {
+ HEIM_TAILQ_HEAD(ad_server_tuple_list, ad_server_tuple) servers;
+};
+
+static int
+sasl_interact(LDAP *ld, unsigned flags, void *defaults, void *interact)
+{
+ return LDAP_SUCCESS;
+}
+
+#ifdef LDAP_OPT_X_SASL_GSS_CREDS
+static krb5_error_code
+ad_acquire_cred(krb5_context context,
+ krb5_const_realm realm,
+ struct ad_server_tuple *server)
+{
+ const char *keytab_name = NULL;
+ char *keytab_name_buf = NULL;
+ krb5_error_code ret;
+
+ OM_uint32 major, minor;
+ gss_key_value_element_desc client_keytab;
+ gss_key_value_set_desc cred_store;
+ gss_OID_set_desc desired_mechs;
+
+ desired_mechs.count = 1;
+ desired_mechs.elements = GSS_KRB5_MECHANISM;
+
+ keytab_name = krb5_config_get_string(context, NULL, "kdc", realm,
+ "gss_altsecid_authorizer_keytab_name", NULL);
+ if (keytab_name == NULL)
+ keytab_name = krb5_config_get_string(context, NULL, "kdc",
+ "gss_altsecid_authorizer_keytab_name", NULL);
+ if (keytab_name == NULL) {
+ ret = _krb5_kt_client_default_name(context, &keytab_name_buf);
+ if (ret)
+ return ret;
+
+ keytab_name = keytab_name_buf;
+ }
+
+ client_keytab.key = "client_keytab";
+ client_keytab.value = keytab_name;
+
+ cred_store.count = 1;
+ cred_store.elements = &client_keytab;
+
+ major = gss_acquire_cred_from(&minor, GSS_C_NO_NAME, GSS_C_INDEFINITE,
+ &desired_mechs, GSS_C_INITIATE,
+ &cred_store, &server->gss_cred, NULL, NULL);
+ if (GSS_ERROR(major))
+ ret = minor ? minor : KRB5_KT_NOTFOUND;
+ else
+ ret = 0;
+
+ krb5_xfree(keytab_name_buf);
+
+ return ret;
+}
+#endif
+
+static krb5_boolean
+is_recoverable_ldap_err_p(int lret)
+{
+ return
+ (lret == LDAP_SERVER_DOWN ||
+ lret == LDAP_TIMEOUT ||
+ lret == LDAP_UNAVAILABLE ||
+ lret == LDAP_BUSY ||
+ lret == LDAP_CONNECT_ERROR);
+}
+
+static krb5_error_code
+ad_connect(krb5_context context,
+ krb5_const_realm realm,
+ struct ad_server_tuple *server)
+{
+ krb5_error_code ret;
+ struct {
+ char *server;
+ int port;
+ } *s, *servers = NULL;
+ size_t i, num_servers = 0;
+
+ {
+ struct rk_dns_reply *r;
+ struct rk_resource_record *rr;
+ char *domain;
+
+ asprintf(&domain, "_ldap._tcp.%s", realm);
+ if (domain == NULL) {
+ ret = krb5_enomem(context);
+ goto out;
+ }
+
+ r = rk_dns_lookup(domain, "SRV");
+ free(domain);
+ if (r == NULL) {
+ krb5_set_error_message(context, KRB5KDC_ERR_SVC_UNAVAILABLE,
+ "Couldn't find AD DC in DNS");
+ ret = KRB5KDC_ERR_SVC_UNAVAILABLE;
+ goto out;
+ }
+
+ for (rr = r->head ; rr != NULL; rr = rr->next) {
+ if (rr->type != rk_ns_t_srv)
+ continue;
+ s = realloc(servers, sizeof(*servers) * (num_servers + 1));
+ if (s == NULL) {
+ ret = krb5_enomem(context);
+ rk_dns_free_data(r);
+ goto out;
+ }
+ servers = s;
+ num_servers++;
+ servers[num_servers - 1].port = rr->u.srv->port;
+ servers[num_servers - 1].server = strdup(rr->u.srv->target);
+ }
+ rk_dns_free_data(r);
+ }
+
+#ifdef LDAP_OPT_X_SASL_GSS_CREDS
+ if (server->gss_cred == GSS_C_NO_CREDENTIAL) {
+ ret = ad_acquire_cred(context, realm, server);
+ if (ret)
+ goto out;
+ }
+#endif
+
+ for (i = 0; i < num_servers; i++) {
+ int lret, version = LDAP_VERSION3;
+ LDAP *ld;
+ char *url = NULL;
+
+ asprintf(&url, "ldap://%s:%d", servers[i].server, servers[i].port);
+ if (url == NULL) {
+ ret = krb5_enomem(context);
+ goto out;
+ }
+
+ lret = ldap_initialize(&ld, url);
+ free(url);
+ if (lret != LDAP_SUCCESS)
+ continue;
+
+ ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version);
+ ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
+#ifdef LDAP_OPT_X_SASL_GSS_CREDS
+ ldap_set_option(ld, LDAP_OPT_X_SASL_GSS_CREDS, server->gss_cred);
+#endif
+
+ lret = ldap_sasl_interactive_bind_s(ld, NULL, "GSS-SPNEGO",
+ NULL, NULL, LDAP_SASL_QUIET,
+ sasl_interact, NULL);
+ if (lret != LDAP_SUCCESS) {
+ krb5_set_error_message(context, 0,
+ "Couldn't bind to AD DC %s:%d: %s",
+ servers[i].server, servers[i].port,
+ ldap_err2string(lret));
+ ldap_unbind_ext_s(ld, NULL, NULL);
+ continue;
+ }
+
+ server->ld = ld;
+ break;
+ }
+
+ ret = (server->ld != NULL) ? 0 : KRB5_KDC_UNREACH;
+
+out:
+ for (i = 0; i < num_servers; i++)
+ free(servers[i].server);
+ free(servers);
+
+ if (ret && server->ld) {
+ ldap_unbind_ext_s(server->ld, NULL, NULL);
+ server->ld = NULL;
+ }
+
+ return ret;
+}
+
+static krb5_error_code
+ad_lookup(krb5_context context,
+ krb5_const_realm realm,
+ struct ad_server_tuple *server,
+ gss_const_name_t initiator_name,
+ gss_const_OID mech_type,
+ krb5_principal *canon_principal,
+ kdc_data_t *requestor_sid)
+{
+ krb5_error_code ret;
+ OM_uint32 minor;
+ const char *mech_type_str, *p;
+ char *filter = NULL;
+ gss_buffer_desc initiator_name_buf = GSS_C_EMPTY_BUFFER;
+ LDAPMessage *m = NULL, *m0;
+ char *basedn = NULL;
+ int lret;
+ char *attrs[] = { "sAMAccountName", "objectSid", NULL };
+ struct berval **values = NULL;
+
+ *canon_principal = NULL;
+ if (requestor_sid)
+ *requestor_sid = NULL;
+
+ mech_type_str = gss_oid_to_name(mech_type);
+ if (mech_type_str == NULL) {
+ ret = KRB5_PREAUTH_BAD_TYPE; /* should never happen */
+ goto out;
+ }
+
+ ret = KRB5_KDC_ERR_CLIENT_NOT_TRUSTED;
+
+ if (GSS_ERROR(gss_display_name(&minor, initiator_name,
+ &initiator_name_buf, NULL)))
+ goto out;
+
+ if ((p = strrchr(mech_type_str, '-')) != NULL) {
+ asprintf(&filter, "(&(objectClass=user)"
+ "(|(altSecurityIdentities=%.*s:%.*s)(altSecurityIdentities=%s:%.*s)))",
+ (int)(p - mech_type_str), mech_type_str,
+ (int)initiator_name_buf.length, (char *)initiator_name_buf.value,
+ mech_type_str,
+ (int)initiator_name_buf.length,
+ (char *)initiator_name_buf.value);
+ } else {
+ asprintf(&filter, "(&(objectClass=user)(altSecurityIdentities=%s:%.*s))",
+ mech_type_str,
+ (int)initiator_name_buf.length,
+ (char *)initiator_name_buf.value);
+ }
+ if (filter == NULL)
+ goto enomem;
+
+ lret = ldap_domain2dn(realm, &basedn);
+ if (lret != LDAP_SUCCESS)
+ goto out;
+
+ lret = ldap_search_ext_s(server->ld, basedn, LDAP_SCOPE_SUBTREE,
+ filter, attrs, 0,
+ NULL, NULL, NULL, 1, &m);
+ if (lret == LDAP_SIZELIMIT_EXCEEDED)
+ ret = KRB5KDC_ERR_PRINCIPAL_NOT_UNIQUE;
+ else if (is_recoverable_ldap_err_p(lret))
+ ret = KRB5KDC_ERR_SVC_UNAVAILABLE;
+ if (lret != LDAP_SUCCESS)
+ goto out;
+
+ m0 = ldap_first_entry(server->ld, m);
+ if (m0 == NULL)
+ goto out;
+
+ values = ldap_get_values_len(server->ld, m0, "sAMAccountName");
+ if (values == NULL ||
+ ldap_count_values_len(values) == 0)
+ goto out;
+
+ ret = krb5_make_principal(context, canon_principal, realm,
+ values[0]->bv_val, NULL);
+ if (ret)
+ goto out;
+
+ if (requestor_sid) {
+ ldap_value_free_len(values);
+
+ values = ldap_get_values_len(server->ld, m0, "objectSid");
+ if (values == NULL ||
+ ldap_count_values_len(values) == 0)
+ goto out;
+
+ *requestor_sid = kdc_data_create(values[0]->bv_val, values[0]->bv_len);
+ if (*requestor_sid == NULL)
+ goto enomem;
+ }
+
+ goto out;
+
+enomem:
+ ret = krb5_enomem(context);
+ goto out;
+
+out:
+ if (ret) {
+ krb5_free_principal(context, *canon_principal);
+ *canon_principal = NULL;
+
+ if (requestor_sid) {
+ kdc_object_release(*requestor_sid);
+ *requestor_sid = NULL;
+ }
+ }
+
+ ldap_value_free_len(values);
+ ldap_msgfree(m);
+ ldap_memfree(basedn);
+ free(filter);
+ gss_release_buffer(&minor, &initiator_name_buf);
+
+ return ret;
+}
+
+static KRB5_LIB_CALL krb5_error_code
+authorize(void *ctx,
+ astgs_request_t r,
+ gss_const_name_t initiator_name,
+ gss_const_OID mech_type,
+ OM_uint32 ret_flags,
+ krb5_boolean *authorized,
+ krb5_principal *mapped_name)
+{
+ struct altsecid_gss_preauth_authorizer_context *c = ctx;
+ struct ad_server_tuple *server = NULL;
+ krb5_error_code ret;
+ krb5_context context = kdc_request_get_context((kdc_request_t)r);
+ const hdb_entry *client = kdc_request_get_client(r);
+ krb5_const_principal server_princ = kdc_request_get_server_princ(r);
+ krb5_const_realm realm = krb5_principal_get_realm(context, client->principal);
+ krb5_boolean reconnect_p = FALSE;
+ krb5_boolean is_tgs;
+ kdc_data_t requestor_sid = NULL;
+
+ *authorized = FALSE;
+ *mapped_name = NULL;
+
+ if (!krb5_principal_is_federated(context, client->principal) ||
+ (ret_flags & GSS_C_ANON_FLAG))
+ return KRB5_PLUGIN_NO_HANDLE;
+
+ is_tgs = krb5_principal_is_krbtgt(context, server_princ);
+
+ HEIM_TAILQ_FOREACH(server, &c->servers, link) {
+ if (strcmp(realm, server->realm) == 0)
+ break;
+ }
+
+ if (server == NULL) {
+ server = calloc(1, sizeof(*server));
+ if (server == NULL)
+ return krb5_enomem(context);
+
+ server->realm = strdup(realm);
+ if (server->realm == NULL) {
+ free(server);
+ return krb5_enomem(context);
+ }
+
+ HEIM_TAILQ_INSERT_HEAD(&c->servers, server, link);
+ }
+
+ do {
+ if (server->ld == NULL) {
+ ret = ad_connect(context, realm, server);
+ if (ret)
+ return ret;
+ }
+
+ ret = ad_lookup(context, realm, server,
+ initiator_name, mech_type,
+ mapped_name, is_tgs ? &requestor_sid : NULL);
+ if (ret == KRB5KDC_ERR_SVC_UNAVAILABLE) {
+ ldap_unbind_ext_s(server->ld, NULL, NULL);
+ server->ld = NULL;
+
+ /* try to reconnect iff we haven't already tried */
+ reconnect_p = !reconnect_p;
+ }
+
+ *authorized = (ret == 0);
+ } while (reconnect_p);
+
+ if (requestor_sid) {
+ kdc_request_set_attribute((kdc_request_t)r,
+ HSTR("org.h5l.gss-pa-requestor-sid"), requestor_sid);
+ kdc_object_release(requestor_sid);
+ }
+
+ return ret;
+}
+
+static KRB5_LIB_CALL krb5_error_code
+finalize_pac(void *ctx, astgs_request_t r)
+{
+ kdc_data_t requestor_sid;
+
+ requestor_sid = kdc_request_get_attribute((kdc_request_t)r,
+ HSTR("org.h5l.gss-pa-requestor-sid"));
+ if (requestor_sid == NULL)
+ return 0;
+
+ kdc_audit_setkv_object((kdc_request_t)r, "gss_requestor_sid", requestor_sid);
+
+ return kdc_request_add_pac_buffer(r, PAC_REQUESTOR_SID,
+ kdc_data_get_data(requestor_sid));
+}
+
+static KRB5_LIB_CALL krb5_error_code
+init(krb5_context context, void **contextp)
+{
+ struct altsecid_gss_preauth_authorizer_context *c;
+
+ c = calloc(1, sizeof(*c));
+ if (c == NULL)
+ return krb5_enomem(context);
+
+ HEIM_TAILQ_INIT(&c->servers);
+
+ *contextp = c;
+ return 0;
+}
+
+static KRB5_LIB_CALL void
+fini(void *context)
+{
+ struct altsecid_gss_preauth_authorizer_context *c = context;
+ struct ad_server_tuple *server, *next;
+ OM_uint32 minor;
+
+ HEIM_TAILQ_FOREACH_SAFE(server, &c->servers, link, next) {
+ free(server->realm);
+ ldap_unbind_ext_s(server->ld, NULL, NULL);
+#ifdef LDAP_OPT_X_SASL_GSS_CREDS
+ gss_release_cred(&minor, &server->gss_cred);
+#endif
+ free(server);
+ }
+}
+
+static krb5plugin_gss_preauth_authorizer_ftable plug_desc =
+ { 1, init, fini, authorize, finalize_pac };
+
+static krb5plugin_gss_preauth_authorizer_ftable *plugs[] = { &plug_desc };
+
+static uintptr_t
+altsecid_gss_preauth_authorizer_get_instance(const char *libname)
+{
+ if (strcmp(libname, "krb5") == 0)
+ return krb5_get_instance(libname);
+ if (strcmp(libname, "kdc") == 0)
+ return kdc_get_instance(libname);
+ return 0;
+}
+
+krb5_plugin_load_ft kdc_gss_preauth_authorizer_plugin_load;
+
+krb5_error_code KRB5_CALLCONV
+kdc_gss_preauth_authorizer_plugin_load(heim_pcontext context,
+ krb5_get_instance_func_t *get_instance,
+ size_t *num_plugins,
+ krb5_plugin_common_ftable_cp **plugins)
+{
+ *get_instance = altsecid_gss_preauth_authorizer_get_instance;
+ *num_plugins = sizeof(plugs) / sizeof(plugs[0]);
+ *plugins = (krb5_plugin_common_ftable_cp *)plugs;
+ return 0;
+}
diff --git a/third_party/heimdal/kdc/announce.c b/third_party/heimdal/kdc/announce.c
new file mode 100644
index 0000000..cf3fdc3
--- /dev/null
+++ b/third_party/heimdal/kdc/announce.c
@@ -0,0 +1,544 @@
+/*
+ * Copyright (c) 2008 Apple Inc. All Rights Reserved.
+ *
+ * Export of this software from the United States of America may require
+ * a specific license from the United States Government. It is the
+ * responsibility of any person or organization contemplating export to
+ * obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of Apple Inc. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Apple Inc. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+#include "kdc_locl.h"
+
+#if defined(__APPLE__) && defined(HAVE_GCD)
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <SystemConfiguration/SCDynamicStore.h>
+#include <SystemConfiguration/SCDynamicStoreCopySpecific.h>
+#include <SystemConfiguration/SCDynamicStoreKey.h>
+
+#include <dispatch/dispatch.h>
+
+#include <asl.h>
+#include <resolv.h>
+
+#include <dns_sd.h>
+#include <err.h>
+
+static krb5_kdc_configuration *announce_config;
+static krb5_context announce_context;
+
+struct entry {
+ DNSRecordRef recordRef;
+ char *domain;
+ char *realm;
+#define F_EXISTS 1
+#define F_PUSH 2
+ int flags;
+ struct entry *next;
+};
+
+/* #define REGISTER_SRV_RR */
+
+static struct entry *g_entries = NULL;
+static CFStringRef g_hostname = NULL;
+static DNSServiceRef g_dnsRef = NULL;
+static SCDynamicStoreRef g_store = NULL;
+static dispatch_queue_t g_queue = NULL;
+
+#define LOG(...) asl_log(NULL, NULL, ASL_LEVEL_INFO, __VA_ARGS__)
+
+static void create_dns_sd(void);
+static void destroy_dns_sd(void);
+static void update_all(SCDynamicStoreRef, CFArrayRef, void *);
+
+
+/* parameters */
+static CFStringRef NetworkChangedKey_BackToMyMac = CFSTR("Setup:/Network/BackToMyMac");
+
+
+static char *
+CFString2utf8(CFStringRef string)
+{
+ size_t size;
+ char *str;
+
+ size = 1 + CFStringGetMaximumSizeForEncoding(CFStringGetLength(string), kCFStringEncodingUTF8);
+ str = malloc(size);
+ if (str == NULL)
+ return NULL;
+
+ if (CFStringGetCString(string, str, size, kCFStringEncodingUTF8) == false) {
+ free(str);
+ return NULL;
+ }
+ return str;
+}
+
+/*
+ *
+ */
+
+static void
+retry_timer(void)
+{
+ dispatch_source_t s;
+ dispatch_time_t t;
+
+ s = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
+ 0, 0, g_queue);
+ t = dispatch_time(DISPATCH_TIME_NOW, 5ull * NSEC_PER_SEC);
+ dispatch_source_set_timer(s, t, 0, NSEC_PER_SEC);
+ dispatch_source_set_event_handler(s, ^{
+ create_dns_sd();
+ dispatch_release(s);
+ });
+ dispatch_resume(s);
+}
+
+/*
+ *
+ */
+
+static void
+create_dns_sd(void)
+{
+ DNSServiceErrorType error;
+ dispatch_source_t s;
+
+ error = DNSServiceCreateConnection(&g_dnsRef);
+ if (error) {
+ retry_timer();
+ return;
+ }
+
+ dispatch_suspend(g_queue);
+
+ s = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
+ DNSServiceRefSockFD(g_dnsRef),
+ 0, g_queue);
+
+ dispatch_source_set_event_handler(s, ^{
+ DNSServiceErrorType ret = DNSServiceProcessResult(g_dnsRef);
+ /* on error tear down and set timer to recreate */
+ if (ret != kDNSServiceErr_NoError && ret != kDNSServiceErr_Transient) {
+ dispatch_source_cancel(s);
+ }
+ });
+
+ dispatch_source_set_cancel_handler(s, ^{
+ destroy_dns_sd();
+ retry_timer();
+ dispatch_release(s);
+ });
+
+ dispatch_resume(s);
+
+ /* Do the first update ourself */
+ update_all(g_store, NULL, NULL);
+ dispatch_resume(g_queue);
+}
+
+static void
+domain_add(const char *domain, const char *realm, int flag)
+{
+ struct entry *e;
+
+ for (e = g_entries; e != NULL; e = e->next) {
+ if (strcmp(domain, e->domain) == 0 && strcmp(realm, e->realm) == 0) {
+ e->flags |= flag;
+ return;
+ }
+ }
+
+ LOG("Adding realm %s to domain %s", realm, domain);
+
+ e = calloc(1, sizeof(*e));
+ if (e == NULL)
+ return;
+ e->domain = strdup(domain);
+ e->realm = strdup(realm);
+ if (e->domain == NULL || e->realm == NULL) {
+ free(e->domain);
+ free(e->realm);
+ free(e);
+ return;
+ }
+ e->flags = flag | F_PUSH; /* if we allocate, we push */
+ e->next = g_entries;
+ g_entries = e;
+}
+
+struct addctx {
+ int flags;
+ const char *realm;
+};
+
+static void
+domains_add(const void *key, const void *value, void *context)
+{
+ char *str = CFString2utf8((CFStringRef)value);
+ struct addctx *ctx = context;
+
+ if (str == NULL)
+ return;
+ if (str[0] != '\0')
+ domain_add(str, ctx->realm, F_EXISTS | ctx->flags);
+ free(str);
+}
+
+
+static void
+dnsCallback(DNSServiceRef sdRef __attribute__((unused)),
+ DNSRecordRef RecordRef __attribute__((unused)),
+ DNSServiceFlags flags __attribute__((unused)),
+ DNSServiceErrorType errorCode __attribute__((unused)),
+ void *context __attribute__((unused)))
+{
+}
+
+#ifdef REGISTER_SRV_RR
+
+/*
+ * Register DNS SRV rr for the realm.
+ */
+
+static const char *register_names[2] = {
+ "_kerberos._tcp",
+ "_kerberos._udp"
+};
+
+static struct {
+ DNSRecordRef *val;
+ size_t len;
+} srvRefs = { NULL, 0 };
+
+static void
+register_srv(const char *realm, const char *hostname, int port)
+{
+ unsigned char target[1024];
+ int i;
+ int size;
+
+ /* skip registering LKDC realms */
+ if (strncmp(realm, "LKDC:", 5) == 0)
+ return;
+
+ /* encode SRV-RR */
+ target[0] = 0; /* priority */
+ target[1] = 0; /* priority */
+ target[2] = 0; /* weight */
+ target[3] = 0; /* weigth */
+ target[4] = (port >> 8) & 0xff; /* port */
+ target[5] = (port >> 0) & 0xff; /* port */
+
+ size = dn_comp(hostname, target + 6, sizeof(target) - 6, NULL, NULL);
+ if (size < 0)
+ return;
+
+ size += 6;
+
+ LOG("register SRV rr for realm %s hostname %s:%d", realm, hostname, port);
+
+ for (i = 0; i < sizeof(register_names)/sizeof(register_names[0]); i++) {
+ char name[kDNSServiceMaxDomainName];
+ DNSServiceErrorType error;
+ void *ptr;
+
+ ptr = realloc(srvRefs.val, sizeof(srvRefs.val[0]) * (srvRefs.len + 1));
+ if (ptr == NULL)
+ errx(1, "malloc: out of memory");
+ srvRefs.val = ptr;
+
+ DNSServiceConstructFullName(name, NULL, register_names[i], realm);
+
+ error = DNSServiceRegisterRecord(g_dnsRef,
+ &srvRefs.val[srvRefs.len],
+ kDNSServiceFlagsUnique | kDNSServiceFlagsShareConnection,
+ 0,
+ name,
+ kDNSServiceType_SRV,
+ kDNSServiceClass_IN,
+ size,
+ target,
+ 0,
+ dnsCallback,
+ NULL);
+ if (error) {
+ LOG("Failed to register SRV rr for realm %s: %d", realm, error);
+ } else
+ srvRefs.len++;
+ }
+}
+
+static void
+unregister_srv_realms(void)
+{
+ if (g_dnsRef) {
+ for (i = 0; i < srvRefs.len; i++)
+ DNSServiceRemoveRecord(g_dnsRef, srvRefs.val[i], 0);
+ }
+ free(srvRefs.val);
+ srvRefs.len = 0;
+ srvRefs.val = NULL;
+}
+
+static void
+register_srv_realms(CFStringRef host)
+{
+ krb5_error_code ret;
+ char *hostname;
+ size_t i;
+
+ /* first unregister old names */
+
+ hostname = CFString2utf8(host);
+ if (hostname == NULL)
+ return;
+
+ for(i = 0; i < announce_config->num_db; i++) {
+ char **realms, **r;
+
+ if (announce_config->db[i]->hdb_get_realms == NULL)
+ continue;
+
+ ret = (announce_config->db[i]->hdb_get_realms)(announce_context, &realms);
+ if (ret == 0) {
+ for (r = realms; r && *r; r++)
+ register_srv(*r, hostname, 88);
+ krb5_free_host_realm(announce_context, realms);
+ }
+ }
+
+ free(hostname);
+}
+#endif /* REGISTER_SRV_RR */
+
+static void
+update_dns(void)
+{
+ DNSServiceErrorType error;
+ struct entry **e = &g_entries;
+ char *hostname;
+
+ hostname = CFString2utf8(g_hostname);
+ if (hostname == NULL)
+ return;
+
+ while (*e != NULL) {
+ /* remove if this wasn't updated */
+ if (((*e)->flags & F_EXISTS) == 0) {
+ struct entry *drop = *e;
+ *e = (*e)->next;
+
+ LOG("Deleting realm %s from domain %s",
+ drop->realm, drop->domain);
+
+ if (drop->recordRef && g_dnsRef)
+ DNSServiceRemoveRecord(g_dnsRef, drop->recordRef, 0);
+ free(drop->domain);
+ free(drop->realm);
+ free(drop);
+ continue;
+ }
+ if ((*e)->flags & F_PUSH) {
+ struct entry *update = *e;
+ char *dnsdata, *name;
+ size_t len;
+
+ len = strlen(update->realm);
+ asprintf(&dnsdata, "%c%s", (int)len, update->realm);
+ if (dnsdata == NULL)
+ errx(1, "malloc");
+
+ asprintf(&name, "_kerberos.%s.%s", hostname, update->domain);
+ if (name == NULL)
+ errx(1, "malloc");
+
+ if (update->recordRef)
+ DNSServiceRemoveRecord(g_dnsRef, update->recordRef, 0);
+
+ error = DNSServiceRegisterRecord(g_dnsRef,
+ &update->recordRef,
+ kDNSServiceFlagsShared | kDNSServiceFlagsAllowRemoteQuery,
+ 0,
+ name,
+ kDNSServiceType_TXT,
+ kDNSServiceClass_IN,
+ len+1,
+ dnsdata,
+ 0,
+ dnsCallback,
+ NULL);
+ free(name);
+ free(dnsdata);
+ if (error)
+ errx(1, "failure to update entry for %s/%s",
+ update->domain, update->realm);
+ }
+ e = &(*e)->next;
+ }
+ free(hostname);
+}
+
+static void
+update_entries(SCDynamicStoreRef store, const char *realm, int flags)
+{
+ CFDictionaryRef btmm;
+
+ /* we always announce in the local domain */
+ domain_add("local", realm, F_EXISTS | flags);
+
+ /* announce btmm */
+ btmm = SCDynamicStoreCopyValue(store, NetworkChangedKey_BackToMyMac);
+ if (btmm) {
+ struct addctx addctx;
+
+ addctx.flags = flags;
+ addctx.realm = realm;
+
+ CFDictionaryApplyFunction(btmm, domains_add, &addctx);
+ CFRelease(btmm);
+ }
+}
+
+static void
+update_all(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info)
+{
+ struct entry *e;
+ CFStringRef host;
+ int i, flags = 0;
+
+ LOG("something changed, running update");
+
+ host = SCDynamicStoreCopyLocalHostName(store);
+ if (host == NULL)
+ return;
+
+ if (g_hostname == NULL || CFStringCompare(host, g_hostname, 0) != kCFCompareEqualTo) {
+ if (g_hostname)
+ CFRelease(g_hostname);
+ g_hostname = CFRetain(host);
+ flags = F_PUSH; /* if hostname has changed, force push */
+
+#ifdef REGISTER_SRV_RR
+ register_srv_realms(g_hostname);
+#endif
+ }
+
+ for (e = g_entries; e != NULL; e = e->next)
+ e->flags &= ~(F_EXISTS|F_PUSH);
+
+ for(i = 0; i < announce_config->num_db; i++) {
+ krb5_error_code ret;
+ char **realms, **r;
+
+ if (announce_config->db[i]->hdb_get_realms == NULL)
+ continue;
+
+ ret = (announce_config->db[i]->hdb_get_realms)(announce_context, announce_config->db[i], &realms);
+ if (ret == 0) {
+ for (r = realms; r && *r; r++)
+ update_entries(store, *r, flags);
+ krb5_free_host_realm(announce_context, realms);
+ }
+ }
+
+ update_dns();
+
+ CFRelease(host);
+}
+
+static void
+delete_all(void)
+{
+ struct entry *e;
+
+ for (e = g_entries; e != NULL; e = e->next)
+ e->flags &= ~(F_EXISTS|F_PUSH);
+
+ update_dns();
+ if (g_entries != NULL)
+ errx(1, "Failed to remove all bonjour entries");
+}
+
+static void
+destroy_dns_sd(void)
+{
+ if (g_dnsRef == NULL)
+ return;
+
+ delete_all();
+#ifdef REGISTER_SRV_RR
+ unregister_srv_realms();
+#endif
+
+ DNSServiceRefDeallocate(g_dnsRef);
+ g_dnsRef = NULL;
+}
+
+
+static SCDynamicStoreRef
+register_notification(void)
+{
+ SCDynamicStoreRef store;
+ CFStringRef computerNameKey;
+ CFMutableArrayRef keys;
+
+ computerNameKey = SCDynamicStoreKeyCreateHostNames(kCFAllocatorDefault);
+
+ store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("Network watcher"),
+ update_all, NULL);
+ if (store == NULL)
+ errx(1, "SCDynamicStoreCreate");
+
+ keys = CFArrayCreateMutable(kCFAllocatorDefault, 2, &kCFTypeArrayCallBacks);
+ if (keys == NULL)
+ errx(1, "CFArrayCreateMutable");
+
+ CFArrayAppendValue(keys, computerNameKey);
+ CFArrayAppendValue(keys, NetworkChangedKey_BackToMyMac);
+
+ if (SCDynamicStoreSetNotificationKeys(store, keys, NULL) == false)
+ errx(1, "SCDynamicStoreSetNotificationKeys");
+
+ CFRelease(computerNameKey);
+ CFRelease(keys);
+
+ if (!SCDynamicStoreSetDispatchQueue(store, g_queue))
+ errx(1, "SCDynamicStoreSetDispatchQueue");
+
+ return store;
+}
+#endif
+
+void
+bonjour_announce(krb5_context context, krb5_kdc_configuration *config)
+{
+#if defined(__APPLE__) && defined(HAVE_GCD)
+ g_queue = dispatch_queue_create("com.apple.kdc_announce", NULL);
+ if (!g_queue)
+ errx(1, "dispatch_queue_create");
+
+ g_store = register_notification();
+ announce_config = config;
+ announce_context = context;
+
+ create_dns_sd();
+#endif
+}
diff --git a/third_party/heimdal/kdc/bx509d.8 b/third_party/heimdal/kdc/bx509d.8
new file mode 100644
index 0000000..f940155
--- /dev/null
+++ b/third_party/heimdal/kdc/bx509d.8
@@ -0,0 +1,480 @@
+.\" Copyright (c) 2020 Kungliga Tekniska Högskolan
+.\" (Royal Institute of Technology, Stockholm, Sweden).
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\"
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\"
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" 3. Neither the name of the Institute nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.Dd January 2, 2020
+.Dt BX509 8
+.Os HEIMDAL
+.Sh NAME
+.Nm bx509d
+.Nd Authentication Bridge for Bearer tokens, Kerberos, and PKIX
+.Sh SYNOPSIS
+.Nm
+.Op Fl h | Fl Fl help
+.Op Fl Fl version
+.Op Fl H Ar HOSTNAME
+.Op Fl d | Fl Fl daemon
+.Op Fl Fl allow-GET
+.Op Fl Fl no-allow-GET
+.Op Fl Fl csrf-protection-type= Ns Ar CSRF-PROTECTION-TYPE
+.Op Fl Fl csrf-header= Ns Ar HEADER-NAME
+.Op Fl Fl csrf-key-file= Ns Ar FILE
+.Op Fl Fl reverse-proxied
+.Op Fl p Ar port number (default: 443)
+.Op Fl Fl cache-dir= Ns Ar DIRECTORY
+.Op Fl Fl cert= Ns Ar HX509-STORE
+.Op Fl Fl private-key= Ns Ar HX509-STORE
+.Op Fl t | Fl Fl thread-per-client
+.Oo Fl v \*(Ba Xo
+.Fl Fl verbose= Ns Ar run verbosely
+.Xc
+.Oc
+.Sh DESCRIPTION
+Serves RESTful (HTTPS) GETs of
+.Ar /get-cert ,
+.Ar /get-tgt ,
+.Ar /get-tgts ,
+and
+.Ar /get-negotiate-token ,
+end-points that implement various experimental Heimdal features:
+.Bl -bullet -compact -offset indent
+.It
+.Li an online CA service over HTTPS,
+.It
+.Li a kinit-over-HTTPS service, and
+.It
+.Li a Negotiate token over HTTPS service.
+.El
+.Pp
+As well, a
+.Ar /health
+service can be used for checking service status.
+.Pp
+Supported options:
+.Bl -tag -width Ds
+.It Xo
+.Fl h ,
+.Fl Fl help
+.Xc
+Print usage message.
+.It Xo
+.Fl Fl version
+.Xc
+Print version.
+.It Xo
+.Fl H Ar HOSTNAME
+.Xc
+Expected audience(s) of bearer tokens (i.e., acceptor name).
+.It Xo
+.Fl Fl allow-GET
+.Xc
+If given, then HTTP GET will be allowed for the various
+end-points other than
+.Ar /health .
+Otherwise only HEAD and POST will be allowed.
+By default GETs are allowed, but this will change soon.
+.It Xo
+.Fl Fl no-allow-GET
+.Xc
+If given then HTTP GETs will be rejected for the various
+end-points other than
+.Ar /health .
+.It Xo
+.Fl Fl csrf-protection-type= Ns Ar CSRF-PROTECTION-TYPE
+.Xc
+Possible values of
+.Ar CSRF-PROTECTION-TYPE
+are
+.Bl -bullet -compact -offset indent
+.It
+.Li GET-with-header
+.It
+.Li GET-with-token
+.It
+.Li POST-with-header
+.It
+.Li POST-with-token
+.El
+This may be given multiple times.
+The default is to require CSRF tokens for POST requests, and to
+require neither a non-simple header nor a CSRF token for GET
+requests.
+.Pp
+See
+.Sx CROSS-SITE REQUEST FORGERY PROTECTION .
+.It Xo
+.Fl Fl csrf-header= Ns Ar HEADER-NAME
+.Xc
+If given, then all requests other than to the
+.Ar /health
+service must have the given request
+.Ar HEADER-NAME
+set (the value is irrelevant).
+The
+.Ar HEADER-NAME
+must be a request header name such that a request having it makes
+it not a
+.Dq simple
+request (see the Cross-Origin Resource Sharing specification).
+Defaults to
+.Ar X-CSRF .
+.It Xo
+.Fl Fl csrf-key-file= Ns Ar FILE
+.Xc
+If given, this file must contain a 16 byte binary key for keying
+the HMAC used in CSRF token construction.
+.It Xo
+.Fl d ,
+.Fl Fl daemon
+.Xc
+Detach from TTY and run in the background.
+.It Xo
+.Fl Fl reverse-proxied
+.Xc
+Serves HTTP instead of HTTPS, accepting only looped-back
+connections.
+.It Xo
+.Fl p Ar port number (default: 443)
+.Xc
+PORT
+.It Xo
+.Fl Fl cache-dir= Ns Ar DIRECTORY
+.Xc
+Directory for various caches.
+If not specified then a temporary directory will be made.
+.It Xo
+.Fl Fl cert= Ns Ar HX509-STORE
+.Xc
+Certificate file path (PEM) for HTTPS service.
+May contain private key as well.
+.It Xo
+.Fl Fl private-key= Ns Ar HX509-STORE
+.Xc
+Private key file path (PEM), if the private key is not stored
+along with the certificiate.
+.It Xo
+.Fl t ,
+.Fl Fl thread-per-client
+.Xc
+Uses a thread per-client instead of as many threads as there are
+CPUs.
+.It Xo
+.Fl v ,
+.Fl Fl verbose= Ns Ar run verbosely
+.Xc
+verbose
+.El
+.Sh HTTP APIS
+All HTTP APIs served by this program accept POSTs, with all
+request parameters given as URI query parameters and/or as
+form data in the POST request body, in either
+.Ar application/x-www-form-urlencoded
+or
+.Ar multipart/formdata .
+If request parameters are given both as URI query parameters
+and as POST forms, then they are merged into a set.
+.Pp
+If GETs are enabled, then request parameters must be supplied
+only as URI query parameters, as GET requests do not have request
+bodies.
+.Pp
+URI query parameters must be of the form
+.Ar param0=value&param1=value...
+.Pp
+Some request parameters can only have one value.
+If multiple values are given for such parameters, then either an
+error will be produced, or only the first URI query parameter
+value will be used, or the first POST form data parameter will be
+used.
+Other request parameters can have multiple values.
+See below.
+.Sh CROSS-SITE REQUEST FORGERY PROTECTION
+.Em None
+of the resources service by this service are intended to be
+executed by web pages.
+.Pp
+All the resources provided by this service are
+.Dq safe
+in the sense that they do not change server-side state besides
+logging, and in that they are idempotent, but they are
+only safe to execute
+.Em if and only if
+the requesting party is trusted to see the response.
+Since none of these resources are intended to be used from web
+pages, it is important that web pages not be able to execute them
+.Em and
+observe the responses.
+.Pp
+In a web browser context, pages from other origins will be able
+to attempt requests to this service, but should never be able to
+see the responses because browsers normally wouldn't allow that.
+Nonetheless, anti cross site request forgery (CSRF) protection
+may be desirable.
+.Pp
+This service provides the following CSRF protection features:
+.Bl -tag -width Ds -offset indent
+.It requests are rejected if they have a
+.Dq Referer
+(except the experimental /get-negotiate-token end-point)
+.It the service can be configured to require a header that would make the
+request not Dq simple
+.It GETs can be disabled (see options), thus requiring POSTs
+.It GETs can be required to have a CSRF token (see below)
+.It POSTs can be required to have a CSRF token
+.El
+.Pp
+The experimental
+.Ar /get-negotiate-token
+end-point, however, always accepts
+.Dq Referer
+requests.
+.Pp
+To obtain a CSRF token, first execute the request without the
+CSRF token, and the resulting error
+response will include a
+.Ar X-CSRF-Token
+response header.
+.Pp
+To execute a request with a CSRF token, first obtain a CSRF token
+as described above, then copy the token to the request as the
+value of the request's
+.Ar X-CSRF-Token
+header.
+.Sh ONLINE CERTIFICATION AUTHORITY HTTP API
+This service provides an HTTP-based Certification Authority (CA).
+CA credentials and configuration are specified in the
+.Va [bx509]
+section of
+.Xr krb5.conf 5 .
+.Pp
+The protocol consists of a
+.Ar GET
+of
+.Ar /get-cert
+with the base-63 encoding of a DER encoding of a PKCS#10
+.Ar CertificationRequest
+(Certificate Signing Request, or CSR) in a
+.Ar csr
+required request parameter.
+In a successful request, the response body will contain a PEM
+encoded end entity certificate and certification chain.
+.Pp
+Or
+.Ar GET
+of
+.Ar /bx509 ,
+as this used to be called.
+.Pp
+Authentication is required.
+Unauthenticated requests will elicit a 401 response.
+.Pp
+Authorization is required.
+Unauthorized requests will elicit a 403 response.
+.Pp
+Subject Alternative Names (SANs) and Extended Key Usage values
+may be requested, both in-band in the CSR as a requested
+extensions attribute, and/or via optional request parameters.
+.Pp
+Supported request parameters:
+.Bl -tag -width Ds -offset indent
+.It Li csr = Va base64-encoded-DER-encoded-CSR
+.It Li dNSName = Va hostname
+.It Li rfc822Name = Va email-address
+.It Li xMPPName = Va XMPP-address
+.It Li krb5PrincipalName = Va Kerberos-principal-name
+.It Li ms-upn = Va UPN
+.It Li eku = Va OID
+.It Li lifetime = Va lifetime
+.El
+.Pp
+More than one name or EKU may be requested.
+.Pp
+Certificate lifetimes are expressed as a decimal number and
+an optional unit (which defaults to
+.Dq day
+).
+.Sh NEGOTIATE TOKEN HTTP API
+This service provides an HTTP-based Negotiate token service.
+This service requires a certification authority (CA) issuer
+credential as it impersonates client principals to the KDC using
+PKINIT client certificates it issues itself.
+.Pp
+The protocol consists of a
+.Ar GET
+of
+.Ar /get-negotiate-token
+with a
+.Ar target = Ar service@host
+request parameter.
+.Pp
+In a successful request, the response body will contain a
+Negotiate token for the authenticated client principal to the
+requested target.
+.Pp
+Authentication is required.
+Unauthenticated requests will elicit a 401 response.
+.Pp
+Subject Alternative Names (SANs) and Extended Key Usage values
+may be requested, both in-band in the CSR as a requested
+extensions attribute, and/or via optional request parameters.
+.Pp
+Supported request parameters:
+.Bl -tag -width Ds -offset indent
+.It Li target = Va service@hostname
+.It Li redirect = Va URI
+.El
+.Pp
+If a redirect URI is given and a matching
+.Va Referer
+header is included in the request, then the response will be a
+redirect to that URI with the Negotiate token in an
+.Va Authorization
+header that the user-agent should copy to the redirected request.
+.Pp
+The certification authority configuration is the same as for the
+.Va /get-cert
+end-point, but as configured in the
+.Va [get-tgt]
+section of
+.Xr krb5.conf 5 .
+.Sh TGT HTTP API
+This service provides an HTTP-based "kinit" service.
+This service requires a certification authority (CA) issuer
+credential as it impersonates client principals to the KDC using
+PKINIT client certificates it issues itself.
+.Pp
+The protocol consists of a
+.Ar GET
+of
+.Ar /get-tgt .
+.Pp
+Supported request parameters:
+.Bl -tag -width Ds -offset indent
+.It Li cname = Va principal-name
+.It Li address = Va IP-address
+.It Li lifetime = Va relative-time
+.El
+.Pp
+In a successful request, the response body will contain a TGT and
+its session key encoded as a "ccache" file contents.
+.Pp
+Authentication is required.
+Unauthenticated requests will elicit a 401 response.
+.Pp
+Authorization is required, where the authorization check is the
+same as for
+.Va /get-cert
+by the authenticated client principal to get a certificate with
+a PKINIT SAN for itself or the requested principal if a
+.Va cname
+request parameter was included.
+.Pp
+Unauthorized requests will elicit a 403 response.
+.Pp
+Requested IP addresses will be added to the issued TGT if
+allowed.
+The IP address of the client will be included if address-less
+TGTs are not allowed.
+See the
+.Va [get-tgt]
+section of
+.Xr krb5.conf 5 .
+.Pp
+The certification authority configuration is the same as for the
+.Va /get-cert
+end-point, but as configured in the
+.Va [get-tgt]
+section of
+.Xr krb5.conf 5 .
+.Sh BATCH TGT HTTP API
+Some sites may have special users that operate batch jobs systems
+and that can impersonate many others by obtaining TGTs for them,
+and which
+.Dq prestash
+credentials for those users in their credentials caches.
+To support these sytems, a
+.Ar GET
+of
+.Ar /get-tgts
+with multiple
+.Ar cname
+request parameters will return those principals' TGTs (if the
+caller is authorized).
+.Pp
+This is similar to the
+.Ar /get-tgt
+end-point, but a) multiple
+.Ar cname
+request parameter values may be given, and b) the caller's
+principal name is not used as a default for the
+.Ar cname
+request parameter.
+The
+.Ar address
+and
+.Ar lifetime
+request parameters are honored.
+.Pp
+For successful
+.Ar GETs
+the response body is a sequence of JSON texts each of which is a
+JSON object with two keys:
+.Bl -tag -width Ds -offset indent
+.It Ar ccache
+with a base64-encoded FILE-type ccache;
+.It Ar name
+the name of the principal whose credentials are in that ccache.
+.El
+.Sh NOTES
+A future release may split all these end-points into separate
+services.
+.Sh ENVIRONMENT
+.Bl -tag -width Ds
+.It Ev KRB5_CONFIG
+The file name of
+.Pa krb5.conf ,
+the default being
+.Pa /etc/krb5.conf .
+.El
+.Sh FILES
+Configuration parameters are specified in
+.Ar /etc/krb5.conf .
+.Bl -tag -width Ds
+.It Pa /etc/krb5.conf
+.El
+.\".Sh EXAMPLES
+.Sh DIAGNOSTICS
+See logging section of
+.Nm krb5.conf.5
+.Sh SEE ALSO
+.Xr krb5.conf 5
+.\".Sh STANDARDS
+.\".Sh HISTORY
+.\".Sh AUTHORS
+.\".Sh BUGS
diff --git a/third_party/heimdal/kdc/bx509d.c b/third_party/heimdal/kdc/bx509d.c
new file mode 100644
index 0000000..793012b
--- /dev/null
+++ b/third_party/heimdal/kdc/bx509d.c
@@ -0,0 +1,3175 @@
+/*
+ * Copyright (c) 2019 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file implements a RESTful HTTPS API to an online CA, as well as an
+ * HTTP/Negotiate token issuer, as well as a way to get TGTs.
+ *
+ * Users are authenticated with Negotiate and/or Bearer.
+ *
+ * This is essentially a RESTful online CA sharing some code with the KDC's
+ * kx509 online CA, and also a proxy for PKINIT and GSS-API (Negotiate).
+ *
+ * See the manual page for HTTP API details.
+ *
+ * TBD:
+ * - rewrite to not use libmicrohttpd but an alternative more appropriate to
+ * Heimdal's license (though libmicrohttpd will do)
+ * - there should be an end-point for fetching an issuer's chain
+ *
+ * NOTES:
+ * - We use krb5_error_code values as much as possible. Where we need to use
+ * MHD_NO because we got that from an mhd function and cannot respond with
+ * an HTTP response, we use (krb5_error_code)-1, and later map that to
+ * MHD_NO.
+ *
+ * (MHD_NO is an ENOMEM-cannot-even-make-a-static-503-response level event.)
+ */
+
+/*
+ * Theory of operation:
+ *
+ * - We use libmicrohttpd (MHD) for the HTTP(S) implementation.
+ *
+ * - MHD has an online request processing model:
+ *
+ * - all requests are handled via the `dh' and `dh_cls' closure arguments
+ * of `MHD_start_daemon()'; ours is called `route()'
+ *
+ * - `dh' is called N+1 times:
+ * - once to allocate a request context
+ * - once for every N chunks of request body
+ * - once to process the request and produce a response
+ *
+ * - the response cannot begin to be produced before consuming the whole
+ * request body (for requests that have a body)
+ * (this seems like a bug in MHD)
+ *
+ * - the response body can be produced over multiple calls (i.e., in an
+ * online manner)
+ *
+ * - Our `route()' processes any POST request body form data / multipart by
+ * treating all the key/value pairs as if they had been additional URI query
+ * parameters.
+ *
+ * - Then `route()' calls a handler appropriate to the URI local-part with the
+ * request context, and the handler produces a response in one call.
+ *
+ * I.e., we turn the online MHD request processing into not-online. Our
+ * handlers are presented with complete requests and must produce complete
+ * responses in one call.
+ *
+ * - `route()' also does any authentication and CSRF protection so that the
+ * request handlers don't have to.
+ *
+ * This non-online request handling approach works for most everything we want
+ * to do. However, for /get-tgts with very large numbers of principals, we
+ * might have to revisit this, using MHD_create_response_from_callback() or
+ * MHD_create_response_from_pipe() (and a thread to do the actual work of
+ * producing the body) instead of MHD_create_response_from_buffer().
+ */
+
+#define _XOPEN_SOURCE_EXTENDED 1
+#define _DEFAULT_SOURCE 1
+#define _BSD_SOURCE 1
+#define _GNU_SOURCE 1
+
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <ctype.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+
+#include <microhttpd.h>
+#include "kdc_locl.h"
+#include "token_validator_plugin.h"
+#include <getarg.h>
+#include <roken.h>
+#include <krb5.h>
+#include <gssapi/gssapi.h>
+#include <gssapi/gssapi_krb5.h>
+#include <hx509.h>
+#include "../lib/hx509/hx_locl.h"
+#include <hx509-private.h>
+
+#define heim_pcontext krb5_context
+#define heim_pconfig krb5_context
+#include <heimbase-svc.h>
+
+#if MHD_VERSION < 0x00097002 || defined(MHD_YES)
+/* libmicrohttpd changed these from int valued macros to an enum in 0.9.71 */
+#ifdef MHD_YES
+#undef MHD_YES
+#undef MHD_NO
+#endif
+enum MHD_Result { MHD_NO = 0, MHD_YES = 1 };
+#define MHD_YES 1
+#define MHD_NO 0
+typedef int heim_mhd_result;
+#else
+typedef enum MHD_Result heim_mhd_result;
+#endif
+
+enum k5_creds_kind { K5_CREDS_EPHEMERAL, K5_CREDS_CACHED };
+
+/*
+ * This is to keep track of memory we need to free, mainly because we had to
+ * duplicate data from the MHD POST form data processor.
+ */
+struct free_tend_list {
+ void *freeme1;
+ void *freeme2;
+ struct free_tend_list *next;
+};
+
+/* Per-request context data structure */
+typedef struct bx509_request_desc {
+ /* Common elements for Heimdal request/response services */
+ HEIM_SVC_REQUEST_DESC_COMMON_ELEMENTS;
+
+ struct MHD_Connection *connection;
+ struct MHD_PostProcessor *pp;
+ struct MHD_Response *response;
+ krb5_times token_times;
+ time_t req_life;
+ hx509_request req;
+ struct free_tend_list *free_list;
+ const char *for_cname;
+ const char *target;
+ const char *redir;
+ const char *method;
+ size_t post_data_size;
+ size_t san_idx; /* For /get-tgts */
+ enum k5_creds_kind cckind;
+ char *pkix_store;
+ char *tgts_filename;
+ FILE *tgts;
+ char *ccname;
+ char *freeme1;
+ char *csrf_token;
+ krb5_addresses tgt_addresses; /* For /get-tgt */
+ char frombuf[128];
+} *bx509_request_desc;
+
+static void
+audit_trail(bx509_request_desc r, krb5_error_code ret)
+{
+ const char *retname = NULL;
+
+ /* Get a symbolic name for some error codes */
+#define CASE(x) case x : retname = #x; break
+ switch (ret) {
+ CASE(ENOMEM);
+ CASE(EACCES);
+ CASE(HDB_ERR_NOT_FOUND_HERE);
+ CASE(HDB_ERR_WRONG_REALM);
+ CASE(HDB_ERR_EXISTS);
+ CASE(HDB_ERR_KVNO_NOT_FOUND);
+ CASE(HDB_ERR_NOENTRY);
+ CASE(HDB_ERR_NO_MKEY);
+ CASE(KRB5KDC_ERR_BADOPTION);
+ CASE(KRB5KDC_ERR_CANNOT_POSTDATE);
+ CASE(KRB5KDC_ERR_CLIENT_NOTYET);
+ CASE(KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN);
+ CASE(KRB5KDC_ERR_ETYPE_NOSUPP);
+ CASE(KRB5KDC_ERR_KEY_EXPIRED);
+ CASE(KRB5KDC_ERR_NAME_EXP);
+ CASE(KRB5KDC_ERR_NEVER_VALID);
+ CASE(KRB5KDC_ERR_NONE);
+ CASE(KRB5KDC_ERR_NULL_KEY);
+ CASE(KRB5KDC_ERR_PADATA_TYPE_NOSUPP);
+ CASE(KRB5KDC_ERR_POLICY);
+ CASE(KRB5KDC_ERR_PREAUTH_FAILED);
+ CASE(KRB5KDC_ERR_PREAUTH_REQUIRED);
+ CASE(KRB5KDC_ERR_SERVER_NOMATCH);
+ CASE(KRB5KDC_ERR_SERVICE_EXP);
+ CASE(KRB5KDC_ERR_SERVICE_NOTYET);
+ CASE(KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN);
+ CASE(KRB5KDC_ERR_TRTYPE_NOSUPP);
+ CASE(KRB5KRB_ERR_RESPONSE_TOO_BIG);
+ /* XXX Add relevant error codes */
+ case 0:
+ retname = "SUCCESS";
+ break;
+ default:
+ retname = NULL;
+ break;
+ }
+
+ /* Let's save a few bytes */
+ if (retname && strncmp("KRB5KDC_", retname, sizeof("KRB5KDC_") - 1) == 0)
+ retname += sizeof("KRB5KDC_") - 1;
+#undef PREFIX
+ heim_audit_trail((heim_svc_req_desc)r, ret, retname);
+}
+
+static krb5_log_facility *logfac;
+static pthread_key_t k5ctx;
+
+static krb5_error_code
+get_krb5_context(krb5_context *contextp)
+{
+ krb5_error_code ret;
+
+ if ((*contextp = pthread_getspecific(k5ctx)))
+ return 0;
+ if ((ret = krb5_init_context(contextp)))
+ return *contextp = NULL, ret;
+ if (logfac)
+ krb5_set_log_dest(*contextp, logfac);
+ (void) pthread_setspecific(k5ctx, *contextp);
+ return *contextp ? 0 : ENOMEM;
+}
+
+typedef enum {
+ CSRF_PROT_UNSPEC = 0,
+ CSRF_PROT_GET_WITH_HEADER = 1,
+ CSRF_PROT_GET_WITH_TOKEN = 2,
+ CSRF_PROT_POST_WITH_HEADER = 8,
+ CSRF_PROT_POST_WITH_TOKEN = 16,
+} csrf_protection_type;
+
+static csrf_protection_type csrf_prot_type = CSRF_PROT_UNSPEC;
+static int port = -1;
+static int allow_GET_flag = -1;
+static int help_flag;
+static int daemonize;
+static int daemon_child_fd = -1;
+static int verbose_counter;
+static int version_flag;
+static int reverse_proxied_flag;
+static int thread_per_client_flag;
+struct getarg_strings audiences;
+static getarg_strings csrf_prot_type_strs;
+static const char *csrf_header = "X-CSRF";
+static const char *cert_file;
+static const char *priv_key_file;
+static const char *cache_dir;
+static const char *csrf_key_file;
+static char *impersonation_key_fn;
+
+static char csrf_key[16];
+
+static krb5_error_code resp(struct bx509_request_desc *, int,
+ enum MHD_ResponseMemoryMode, const char *,
+ const void *, size_t, const char *);
+static krb5_error_code bad_req(struct bx509_request_desc *, krb5_error_code, int,
+ const char *, ...)
+ HEIMDAL_PRINTF_ATTRIBUTE((__printf__, 4, 5));
+
+static krb5_error_code bad_enomem(struct bx509_request_desc *, krb5_error_code);
+static krb5_error_code bad_400(struct bx509_request_desc *, krb5_error_code, char *);
+static krb5_error_code bad_401(struct bx509_request_desc *, char *);
+static krb5_error_code bad_403(struct bx509_request_desc *, krb5_error_code, char *);
+static krb5_error_code bad_404(struct bx509_request_desc *, const char *);
+static krb5_error_code bad_405(struct bx509_request_desc *, const char *);
+static krb5_error_code bad_500(struct bx509_request_desc *, krb5_error_code, const char *);
+static krb5_error_code bad_503(struct bx509_request_desc *, krb5_error_code, const char *);
+static heim_mhd_result validate_csrf_token(struct bx509_request_desc *r);
+
+static int
+validate_token(struct bx509_request_desc *r)
+{
+ krb5_error_code ret;
+ krb5_principal cprinc = NULL;
+ const char *token;
+ const char *host;
+ char token_type[64]; /* Plenty */
+ char *p;
+ krb5_data tok;
+ size_t host_len, brk, i;
+
+ memset(&r->token_times, 0, sizeof(r->token_times));
+ host = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_HOST);
+ if (host == NULL)
+ return bad_400(r, EINVAL, "Host header is missing");
+
+ /* Exclude port number here (IPv6-safe because of the below) */
+ host_len = ((p = strchr(host, ':'))) ? p - host : strlen(host);
+
+ token = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_AUTHORIZATION);
+ if (token == NULL)
+ return bad_401(r, "Authorization token is missing");
+ brk = strcspn(token, " \t");
+ if (token[brk] == '\0' || brk > sizeof(token_type) - 1)
+ return bad_401(r, "Authorization token is missing");
+ memcpy(token_type, token, brk);
+ token_type[brk] = '\0';
+ token += brk + 1;
+ tok.length = strlen(token);
+ tok.data = (void *)(uintptr_t)token;
+
+ for (i = 0; i < audiences.num_strings; i++)
+ if (strncasecmp(host, audiences.strings[i], host_len) == 0 &&
+ audiences.strings[i][host_len] == '\0')
+ break;
+ if (i == audiences.num_strings)
+ return bad_403(r, EINVAL, "Host: value is not accepted here");
+
+ r->sname = strdup(host); /* No need to check for ENOMEM here */
+
+ ret = kdc_validate_token(r->context, NULL /* realm */, token_type, &tok,
+ (const char **)&audiences.strings[i], 1,
+ &cprinc, &r->token_times);
+ if (ret)
+ return bad_403(r, ret, "Token validation failed");
+ if (cprinc == NULL)
+ return bad_403(r, ret, "Could not extract a principal name "
+ "from token");
+ ret = krb5_unparse_name(r->context, cprinc, &r->cname);
+ krb5_free_principal(r->context, cprinc);
+ if (ret)
+ return bad_503(r, ret, "Could not parse principal name");
+ return ret;
+}
+
+static void
+generate_key(hx509_context context,
+ const char *key_name,
+ const char *gen_type,
+ unsigned long gen_bits,
+ char **fn)
+{
+ struct hx509_generate_private_context *key_gen_ctx = NULL;
+ hx509_private_key key = NULL;
+ hx509_certs certs = NULL;
+ hx509_cert cert = NULL;
+ int ret;
+
+ if (strcmp(gen_type, "rsa") != 0)
+ errx(1, "Only RSA keys are supported at this time");
+
+ if (asprintf(fn, "PEM-FILE:%s/.%s_priv_key.pem",
+ cache_dir, key_name) == -1 ||
+ *fn == NULL)
+ err(1, "Could not set up private key for %s", key_name);
+
+ ret = _hx509_generate_private_key_init(context,
+ ASN1_OID_ID_PKCS1_RSAENCRYPTION,
+ &key_gen_ctx);
+ if (ret == 0)
+ ret = _hx509_generate_private_key_bits(context, key_gen_ctx, gen_bits);
+ if (ret == 0)
+ ret = _hx509_generate_private_key(context, key_gen_ctx, &key);
+ if (ret == 0)
+ cert = hx509_cert_init_private_key(context, key, NULL);
+ if (ret == 0)
+ ret = hx509_certs_init(context, *fn,
+ HX509_CERTS_CREATE | HX509_CERTS_UNPROTECT_ALL,
+ NULL, &certs);
+ if (ret == 0)
+ ret = hx509_certs_add(context, certs, cert);
+ if (ret == 0)
+ ret = hx509_certs_store(context, certs, 0, NULL);
+ if (ret)
+ hx509_err(context, 1, ret, "Could not generate and save private key "
+ "for %s", key_name);
+
+ _hx509_generate_private_key_free(&key_gen_ctx);
+ hx509_private_key_free(&key);
+ hx509_certs_free(&certs);
+ hx509_cert_free(cert);
+}
+
+static void
+k5_free_context(void *ctx)
+{
+ krb5_free_context(ctx);
+}
+
+#ifndef HAVE_UNLINKAT
+static int
+unlink1file(const char *dname, const char *name)
+{
+ char p[PATH_MAX];
+
+ if (strlcpy(p, dname, sizeof(p)) < sizeof(p) &&
+ strlcat(p, "/", sizeof(p)) < sizeof(p) &&
+ strlcat(p, name, sizeof(p)) < sizeof(p))
+ return unlink(p);
+ return ERANGE;
+}
+#endif
+
+static void
+rm_cache_dir(void)
+{
+ struct dirent *e;
+ DIR *d;
+
+ /*
+ * This works, but not on Win32:
+ *
+ * (void) simple_execlp("rm", "rm", "-rf", cache_dir, NULL);
+ *
+ * We make no directories in `cache_dir', so we need not recurse.
+ */
+ if ((d = opendir(cache_dir)) == NULL)
+ return;
+
+ while ((e = readdir(d))) {
+#ifdef HAVE_UNLINKAT
+ /*
+ * Because unlinkat() takes a directory FD, implementing one for
+ * libroken is tricky at best. Instead we might want to implement an
+ * rm_dash_rf() function in lib/roken.
+ */
+ (void) unlinkat(dirfd(d), e->d_name, 0);
+#else
+ (void) unlink1file(cache_dir, e->d_name);
+#endif
+ }
+ (void) closedir(d);
+ (void) rmdir(cache_dir);
+}
+
+static krb5_error_code
+mk_pkix_store(char **pkix_store)
+{
+ char *s = NULL;
+ int ret = ENOMEM;
+ int fd;
+
+ if (*pkix_store) {
+ const char *fn = strchr(*pkix_store, ':');
+
+ fn = fn ? fn + 1 : *pkix_store;
+ (void) unlink(fn);
+ }
+
+ free(*pkix_store);
+ *pkix_store = NULL;
+ if (asprintf(&s, "PEM-FILE:%s/pkix-XXXXXX", cache_dir) == -1 ||
+ s == NULL) {
+ free(s);
+ return ret;
+ }
+ if ((fd = mkstemp(s + sizeof("PEM-FILE:") - 1)) == -1) {
+ free(s);
+ return errno;
+ }
+ (void) close(fd);
+ *pkix_store = s;
+ return 0;
+}
+
+static krb5_error_code
+resp(struct bx509_request_desc *r,
+ int http_status_code,
+ enum MHD_ResponseMemoryMode rmmode,
+ const char *content_type,
+ const void *body,
+ size_t bodylen,
+ const char *token)
+{
+ int mret = MHD_YES;
+
+ if (r->response)
+ return MHD_YES;
+
+ (void) gettimeofday(&r->tv_end, NULL);
+ if (http_status_code == MHD_HTTP_OK ||
+ http_status_code == MHD_HTTP_TEMPORARY_REDIRECT)
+ audit_trail(r, 0);
+
+ r->response = MHD_create_response_from_buffer(bodylen, rk_UNCONST(body),
+ rmmode);
+ if (r->response == NULL)
+ return -1;
+ if (r->csrf_token)
+ mret = MHD_add_response_header(r->response, "X-CSRF-Token", r->csrf_token);
+ if (mret == MHD_YES)
+ mret = MHD_add_response_header(r->response, MHD_HTTP_HEADER_CACHE_CONTROL,
+ "no-store, max-age=0");
+ if (mret == MHD_YES && http_status_code == MHD_HTTP_UNAUTHORIZED) {
+ mret = MHD_add_response_header(r->response,
+ MHD_HTTP_HEADER_WWW_AUTHENTICATE,
+ "Bearer");
+ if (mret == MHD_YES)
+ mret = MHD_add_response_header(r->response,
+ MHD_HTTP_HEADER_WWW_AUTHENTICATE,
+ "Negotiate");
+ } else if (mret == MHD_YES && http_status_code == MHD_HTTP_TEMPORARY_REDIRECT) {
+ const char *redir;
+
+ /* XXX Move this */
+ redir = MHD_lookup_connection_value(r->connection, MHD_GET_ARGUMENT_KIND,
+ "redirect");
+ mret = MHD_add_response_header(r->response, MHD_HTTP_HEADER_LOCATION,
+ redir);
+ if (mret != MHD_NO && token)
+ mret = MHD_add_response_header(r->response,
+ MHD_HTTP_HEADER_AUTHORIZATION,
+ token);
+ }
+ if (mret == MHD_YES && content_type) {
+ mret = MHD_add_response_header(r->response,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ content_type);
+ }
+ if (mret == MHD_YES)
+ mret = MHD_queue_response(r->connection, http_status_code, r->response);
+ MHD_destroy_response(r->response);
+ return mret == MHD_NO ? -1 : 0;
+}
+
+static krb5_error_code
+bad_reqv(struct bx509_request_desc *r,
+ krb5_error_code code,
+ int http_status_code,
+ const char *fmt,
+ va_list ap)
+{
+ krb5_error_code ret;
+ const char *k5msg = NULL;
+ const char *emsg = NULL;
+ char *formatted = NULL;
+ char *msg = NULL;
+
+ heim_audit_setkv_number((heim_svc_req_desc)r, "http-status-code",
+ http_status_code);
+ (void) gettimeofday(&r->tv_end, NULL);
+ if (code == ENOMEM) {
+ if (r->context)
+ krb5_log_msg(r->context, logfac, 1, NULL, "Out of memory");
+ audit_trail(r, code);
+ return resp(r, http_status_code, MHD_RESPMEM_PERSISTENT,
+ NULL, fmt, strlen(fmt), NULL);
+ }
+
+ if (code) {
+ if (r->context)
+ emsg = k5msg = krb5_get_error_message(r->context, code);
+ else if (code > -1)
+ emsg = strerror(code);
+ else
+ emsg = "Unknown error";
+ }
+
+ ret = vasprintf(&formatted, fmt, ap);
+ if (code) {
+ if (ret > -1 && formatted)
+ ret = asprintf(&msg, "%s: %s (%d)", formatted, emsg, (int)code);
+ } else {
+ msg = formatted;
+ formatted = NULL;
+ }
+ heim_audit_addreason((heim_svc_req_desc)r, "%s", msg);
+ audit_trail(r, code);
+ if (r->context)
+ krb5_free_error_message(r->context, k5msg);
+
+ if (ret == -1 || msg == NULL) {
+ if (r->context)
+ krb5_log_msg(r->context, logfac, 1, NULL, "Out of memory");
+ return resp(r, MHD_HTTP_SERVICE_UNAVAILABLE, MHD_RESPMEM_PERSISTENT,
+ NULL, "Out of memory", sizeof("Out of memory") - 1, NULL);
+ }
+
+ ret = resp(r, http_status_code, MHD_RESPMEM_MUST_COPY,
+ NULL, msg, strlen(msg), NULL);
+ free(formatted);
+ free(msg);
+ return ret == -1 ? -1 : code;
+}
+
+static krb5_error_code
+bad_req(struct bx509_request_desc *r,
+ krb5_error_code code,
+ int http_status_code,
+ const char *fmt,
+ ...)
+{
+ krb5_error_code ret;
+ va_list ap;
+
+ va_start(ap, fmt);
+ ret = bad_reqv(r, code, http_status_code, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+static krb5_error_code
+bad_enomem(struct bx509_request_desc *r, krb5_error_code ret)
+{
+ return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
+ "Out of memory");
+}
+
+static krb5_error_code
+bad_400(struct bx509_request_desc *r, int ret, char *reason)
+{
+ return bad_req(r, ret, MHD_HTTP_BAD_REQUEST, "%s", reason);
+}
+
+static krb5_error_code
+bad_401(struct bx509_request_desc *r, char *reason)
+{
+ return bad_req(r, EACCES, MHD_HTTP_UNAUTHORIZED, "%s", reason);
+}
+
+static krb5_error_code
+bad_403(struct bx509_request_desc *r, krb5_error_code ret, char *reason)
+{
+ return bad_req(r, ret, MHD_HTTP_FORBIDDEN, "%s", reason);
+}
+
+static krb5_error_code
+bad_404(struct bx509_request_desc *r, const char *name)
+{
+ return bad_req(r, ENOENT, MHD_HTTP_NOT_FOUND,
+ "Resource not found: %s", name);
+}
+
+static krb5_error_code
+bad_405(struct bx509_request_desc *r, const char *method)
+{
+ return bad_req(r, EPERM, MHD_HTTP_METHOD_NOT_ALLOWED,
+ "Method not supported: %s", method);
+}
+
+static krb5_error_code
+bad_413(struct bx509_request_desc *r)
+{
+ return bad_req(r, E2BIG, MHD_HTTP_METHOD_NOT_ALLOWED,
+ "POST request body too large");
+}
+
+static krb5_error_code
+bad_500(struct bx509_request_desc *r,
+ krb5_error_code ret,
+ const char *reason)
+{
+ return bad_req(r, ret, MHD_HTTP_INTERNAL_SERVER_ERROR,
+ "Internal error: %s", reason);
+}
+
+static krb5_error_code
+bad_503(struct bx509_request_desc *r,
+ krb5_error_code ret,
+ const char *reason)
+{
+ return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
+ "Service unavailable: %s", reason);
+}
+
+static krb5_error_code
+good_bx509(struct bx509_request_desc *r)
+{
+ krb5_error_code ret;
+ const char *fn;
+ size_t bodylen;
+ void *body;
+
+ /*
+ * This `fn' thing is just to quiet linters that think "hey, strchr() can
+ * return NULL so...", but here we've build `r->pkix_store' and know it has
+ * a ':'.
+ */
+ if (r->pkix_store == NULL)
+ return bad_503(r, EINVAL, "Internal error"); /* Quiet warnings */
+ fn = strchr(r->pkix_store, ':');
+ fn = fn ? fn + 1 : r->pkix_store;
+ ret = rk_undumpdata(fn, &body, &bodylen);
+ if (ret)
+ return bad_503(r, ret, "Could not recover issued certificate "
+ "from PKIX store");
+
+ (void) gettimeofday(&r->tv_end, NULL);
+ ret = resp(r, MHD_HTTP_OK, MHD_RESPMEM_MUST_COPY, "application/x-pem-file",
+ body, bodylen, NULL);
+ free(body);
+ return ret;
+}
+
+static heim_mhd_result
+bx509_param_cb(void *d,
+ enum MHD_ValueKind kind,
+ const char *key,
+ const char *val)
+{
+ struct bx509_request_desc *r = d;
+ heim_oid oid = { 0, 0 };
+
+ if (strcmp(key, "eku") == 0 && val) {
+ heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS, "requested_eku",
+ "%s", val);
+ r->error_code = der_parse_heim_oid(val, ".", &oid);
+ if (r->error_code == 0)
+ r->error_code = hx509_request_add_eku(r->context->hx509ctx, r->req, &oid);
+ der_free_oid(&oid);
+ } else if (strcmp(key, "dNSName") == 0 && val) {
+ heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
+ "requested_dNSName", "%s", val);
+ r->error_code = hx509_request_add_dns_name(r->context->hx509ctx, r->req, val);
+ } else if (strcmp(key, "rfc822Name") == 0 && val) {
+ heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
+ "requested_rfc822Name", "%s", val);
+ r->error_code = hx509_request_add_email(r->context->hx509ctx, r->req, val);
+ } else if (strcmp(key, "xMPPName") == 0 && val) {
+ heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
+ "requested_xMPPName", "%s", val);
+ r->error_code = hx509_request_add_xmpp_name(r->context->hx509ctx, r->req,
+ val);
+ } else if (strcmp(key, "krb5PrincipalName") == 0 && val) {
+ heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
+ "requested_krb5PrincipalName", "%s", val);
+ r->error_code = hx509_request_add_pkinit(r->context->hx509ctx, r->req,
+ val);
+ } else if (strcmp(key, "ms-upn") == 0 && val) {
+ heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
+ "requested_ms_upn", "%s", val);
+ r->error_code = hx509_request_add_ms_upn_name(r->context->hx509ctx, r->req,
+ val);
+ } else if (strcmp(key, "registeredID") == 0 && val) {
+ heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
+ "requested_registered_id", "%s", val);
+ r->error_code = der_parse_heim_oid(val, ".", &oid);
+ if (r->error_code == 0)
+ r->error_code = hx509_request_add_registered(r->context->hx509ctx, r->req,
+ &oid);
+ der_free_oid(&oid);
+ } else if (strcmp(key, "csr") == 0 && val) {
+ heim_audit_setkv_bool((heim_svc_req_desc)r, "requested_csr", TRUE);
+ r->error_code = 0; /* Handled upstairs */
+ } else if (strcmp(key, "lifetime") == 0 && val) {
+ r->req_life = parse_time(val, "day");
+ } else {
+ /* Produce error for unknown params */
+ heim_audit_setkv_bool((heim_svc_req_desc)r, "requested_unknown", TRUE);
+ krb5_set_error_message(r->context, r->error_code = ENOTSUP,
+ "Query parameter %s not supported", key);
+ }
+ return r->error_code == 0 ? MHD_YES : MHD_NO /* Stop iterating */;
+}
+
+static krb5_error_code
+authorize_CSR(struct bx509_request_desc *r,
+ krb5_data *csr,
+ krb5_const_principal p)
+{
+ krb5_error_code ret;
+
+ ret = hx509_request_parse_der(r->context->hx509ctx, csr, &r->req);
+ if (ret)
+ return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
+ "Could not parse CSR");
+ r->error_code = 0;
+ (void) MHD_get_connection_values(r->connection, MHD_GET_ARGUMENT_KIND,
+ bx509_param_cb, r);
+ ret = r->error_code;
+ if (ret)
+ return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
+ "Could not handle query parameters");
+
+ ret = kdc_authorize_csr(r->context, "bx509", r->req, p);
+ if (ret)
+ return bad_403(r, ret, "Not authorized to requested certificate");
+ return ret;
+}
+
+/*
+ * hx509_certs_iter_f() callback to assign a private key to the first cert in a
+ * store.
+ */
+static int HX509_LIB_CALL
+set_priv_key(hx509_context context, void *d, hx509_cert c)
+{
+ (void) _hx509_cert_assign_key(c, (hx509_private_key)d);
+ return -1; /* stop iteration */
+}
+
+static krb5_error_code
+store_certs(hx509_context context,
+ const char *store,
+ hx509_certs store_these,
+ hx509_private_key key)
+{
+ krb5_error_code ret;
+ hx509_certs certs = NULL;
+
+ ret = hx509_certs_init(context, store, HX509_CERTS_CREATE, NULL,
+ &certs);
+ if (ret == 0) {
+ if (key)
+ (void) hx509_certs_iter_f(context, store_these, set_priv_key, key);
+ hx509_certs_merge(context, certs, store_these);
+ }
+ if (ret == 0)
+ hx509_certs_store(context, certs, 0, NULL);
+ hx509_certs_free(&certs);
+ return ret;
+}
+
+/* Setup a CSR for bx509() */
+static krb5_error_code
+do_CA(struct bx509_request_desc *r, const char *csr)
+{
+ krb5_error_code ret = 0;
+ krb5_principal p;
+ hx509_certs certs = NULL;
+ krb5_data d;
+ ssize_t bytes;
+ char *csr2, *q;
+
+ /*
+ * Work around bug where microhttpd decodes %2b to + then + to space. That
+ * bug does not affect other base64 special characters that get URI
+ * %-encoded.
+ */
+ if ((csr2 = strdup(csr)) == NULL)
+ return bad_enomem(r, ENOMEM);
+ for (q = strchr(csr2, ' '); q; q = strchr(q + 1, ' '))
+ *q = '+';
+
+ ret = krb5_parse_name(r->context, r->cname, &p);
+ if (ret) {
+ free(csr2);
+ return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
+ "Could not parse principal name");
+ }
+
+ /* Set CSR */
+ if ((d.data = malloc(strlen(csr2))) == NULL) {
+ krb5_free_principal(r->context, p);
+ free(csr2);
+ return bad_enomem(r, ENOMEM);
+ }
+
+ bytes = rk_base64_decode(csr2, d.data);
+ free(csr2);
+ if (bytes < 0)
+ ret = errno ? errno : EINVAL;
+ else
+ d.length = bytes;
+ if (ret) {
+ krb5_free_principal(r->context, p);
+ free(d.data);
+ return bad_500(r, ret, "Invalid base64 encoding of CSR");
+ }
+
+ /*
+ * Parses and validates the CSR, adds external extension requests from
+ * query parameters, then checks authorization.
+ */
+ ret = authorize_CSR(r, &d, p);
+ free(d.data);
+ d.data = 0;
+ d.length = 0;
+ if (ret) {
+ krb5_free_principal(r->context, p);
+ return ret; /* authorize_CSR() calls bad_req() */
+ }
+
+ /* Issue the certificate */
+ ret = kdc_issue_certificate(r->context, "bx509", logfac, r->req, p,
+ &r->token_times, r->req_life,
+ 1 /* send_chain */, &certs);
+ krb5_free_principal(r->context, p);
+ if (ret) {
+ if (ret == KRB5KDC_ERR_POLICY || ret == EACCES)
+ return bad_403(r, ret,
+ "Certificate request denied for policy reasons");
+ return bad_500(r, ret, "Certificate issuance failed");
+ }
+
+ /* Setup PKIX store */
+ if ((ret = mk_pkix_store(&r->pkix_store)))
+ return bad_500(r, ret,
+ "Could not create PEM store for issued certificate");
+
+ ret = store_certs(r->context->hx509ctx, r->pkix_store, certs, NULL);
+ hx509_certs_free(&certs);
+ if (ret)
+ return bad_500(r, ret, "Failed to convert issued"
+ " certificate and chain to PEM");
+ return 0;
+}
+
+/* Copied from kdc/connect.c */
+static void
+addr_to_string(krb5_context context,
+ struct sockaddr *addr,
+ char *str,
+ size_t len)
+{
+ krb5_error_code ret;
+ krb5_address a;
+
+ ret = krb5_sockaddr2address(context, addr, &a);
+ if (ret == 0) {
+ ret = krb5_print_address(&a, str, len, &len);
+ krb5_free_address(context, &a);
+ }
+ if (ret)
+ snprintf(str, len, "<family=%d>", addr->sa_family);
+}
+
+static void clean_req_desc(struct bx509_request_desc *);
+
+static krb5_error_code
+set_req_desc(struct MHD_Connection *connection,
+ const char *method,
+ const char *url,
+ struct bx509_request_desc **rp)
+{
+ struct bx509_request_desc *r;
+ const union MHD_ConnectionInfo *ci;
+ const char *token;
+ krb5_error_code ret;
+
+ *rp = NULL;
+ if ((r = calloc(1, sizeof(*r))) == NULL)
+ return ENOMEM;
+ (void) gettimeofday(&r->tv_start, NULL);
+
+ ret = get_krb5_context(&r->context);
+ r->connection = connection;
+ r->response = NULL;
+ r->pp = NULL;
+ r->request.data = "<HTTP-REQUEST>";
+ r->request.length = sizeof("<HTTP-REQUEST>");
+ r->from = r->frombuf;
+ r->tgt_addresses.len = 0;
+ r->tgt_addresses.val = 0;
+ r->hcontext = r->context ? r->context->hcontext : NULL;
+ r->config = NULL;
+ r->logf = logfac;
+ r->csrf_token = NULL;
+ r->free_list = NULL;
+ r->method = method;
+ r->reqtype = url;
+ r->target = r->redir = NULL;
+ r->pkix_store = NULL;
+ r->for_cname = NULL;
+ r->freeme1 = NULL;
+ r->reason = NULL;
+ r->tgts_filename = NULL;
+ r->tgts = NULL;
+ r->ccname = NULL;
+ r->reply = NULL;
+ r->sname = NULL;
+ r->cname = NULL;
+ r->addr = NULL;
+ r->req = NULL;
+ r->req_life = 0;
+ r->error_code = ret;
+ r->kv = heim_dict_create(10);
+ r->attributes = heim_dict_create(1);
+ if (ret == 0 && (r->kv == NULL || r->attributes == NULL))
+ r->error_code = ret = ENOMEM;
+ ci = MHD_get_connection_info(connection,
+ MHD_CONNECTION_INFO_CLIENT_ADDRESS);
+ if (ci) {
+ r->addr = ci->client_addr;
+ addr_to_string(r->context, r->addr, r->frombuf, sizeof(r->frombuf));
+ }
+
+ heim_audit_addkv((heim_svc_req_desc)r, 0, "method", "GET");
+ heim_audit_addkv((heim_svc_req_desc)r, 0, "endpoint", "%s", r->reqtype);
+ token = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_AUTHORIZATION);
+ if (token && r->kv) {
+ const char *token_end;
+
+ if ((token_end = strchr(token, ' ')) == NULL ||
+ (token_end - token) > INT_MAX || (token_end - token) < 2)
+ heim_audit_addkv((heim_svc_req_desc)r, 0, "auth", "<unknown>");
+ else
+ heim_audit_addkv((heim_svc_req_desc)r, 0, "auth", "%.*s",
+ (int)(token_end - token), token);
+
+ }
+
+ if (ret == 0)
+ *rp = r;
+ else
+ clean_req_desc(r);
+ return ret;
+}
+
+static void
+clean_req_desc(struct bx509_request_desc *r)
+{
+ if (!r)
+ return;
+ while (r->free_list) {
+ struct free_tend_list *ftl = r->free_list;
+ r->free_list = r->free_list->next;
+ free(ftl->freeme1);
+ free(ftl->freeme2);
+ free(ftl);
+ }
+ if (r->pkix_store) {
+ const char *fn = strchr(r->pkix_store, ':');
+
+ /*
+ * This `fn' thing is just to quiet linters that think "hey, strchr() can
+ * return NULL so...", but here we've build `r->pkix_store' and know it has
+ * a ':'.
+ */
+ fn = fn ? fn + 1 : r->pkix_store;
+ (void) unlink(fn);
+ }
+ krb5_free_addresses(r->context, &r->tgt_addresses);
+ hx509_request_free(&r->req);
+ heim_release(r->attributes);
+ heim_release(r->reason);
+ heim_release(r->kv);
+ if (r->ccname && r->cckind == K5_CREDS_EPHEMERAL) {
+ const char *fn = r->ccname;
+
+ if (strncmp(fn, "FILE:", sizeof("FILE:") - 1) == 0)
+ fn += sizeof("FILE:") - 1;
+ (void) unlink(fn);
+ }
+ if (r->tgts)
+ (void) fclose(r->tgts);
+ if (r->tgts_filename) {
+ (void) unlink(r->tgts_filename);
+ free(r->tgts_filename);
+ }
+ /* No need to destroy r->response */
+ if (r->pp)
+ MHD_destroy_post_processor(r->pp);
+ free(r->csrf_token);
+ free(r->pkix_store);
+ free(r->freeme1);
+ free(r->ccname);
+ free(r->cname);
+ free(r->sname);
+ free(r);
+}
+
+/* Implements GETs of /bx509 */
+static krb5_error_code
+bx509(struct bx509_request_desc *r)
+{
+ krb5_error_code ret;
+ const char *csr;
+
+ /* Get required inputs */
+ csr = MHD_lookup_connection_value(r->connection, MHD_GET_ARGUMENT_KIND,
+ "csr");
+ if (csr == NULL)
+ return bad_400(r, EINVAL, "CSR is missing");
+
+ if (r->cname == NULL)
+ return bad_403(r, EINVAL,
+ "Could not extract principal name from token");
+
+ /* Parse CSR, add extensions from parameters, authorize, issue cert */
+ if ((ret = do_CA(r, csr)))
+ return ret;
+
+ /* Read and send the contents of the PKIX store */
+ krb5_log_msg(r->context, logfac, 1, NULL, "Issued certificate to %s",
+ r->cname);
+ return good_bx509(r);
+}
+
+/*
+ * princ_fs_encode_sz() and princ_fs_encode() encode a principal name to be
+ * safe for use as a file name. They function very much like URL encoders, but
+ * '~' and '.' also get encoded, and '@' does not.
+ *
+ * A corresponding decoder is not needed.
+ *
+ * XXX Maybe use krb5_cc_default_for()!
+ */
+static size_t
+princ_fs_encode_sz(const char *in)
+{
+ size_t sz = strlen(in);
+
+ while (*in) {
+ unsigned char c = *(const unsigned char *)(in++);
+
+ if (isalnum(c))
+ continue;
+ switch (c) {
+ case '@':
+ case '-':
+ case '_':
+ continue;
+ default:
+ sz += 2;
+ }
+ }
+ return sz;
+}
+
+static char *
+princ_fs_encode(const char *in)
+{
+ size_t len = strlen(in);
+ size_t sz = princ_fs_encode_sz(in);
+ size_t i, k;
+ char *s;
+
+ if ((s = malloc(sz + 1)) == NULL)
+ return NULL;
+ s[sz] = '\0';
+
+ for (i = k = 0; i < len; i++) {
+ char c = in[i];
+
+ switch (c) {
+ case '@':
+ case '-':
+ case '_':
+ s[k++] = c;
+ break;
+ default:
+ if (isalnum((unsigned char)c)) {
+ s[k++] = c;
+ } else {
+ s[k++] = '%';
+ s[k++] = "0123456789abcdef"[(c&0xff)>>4];
+ s[k++] = "0123456789abcdef"[(c&0x0f)];
+ }
+ }
+ }
+ return s;
+}
+
+
+/*
+ * Find an existing, live ccache for `princ' in `cache_dir' or acquire Kerberos
+ * creds for `princ' with PKINIT and put them in a ccache in `cache_dir'.
+ */
+static krb5_error_code
+find_ccache(krb5_context context, const char *princ, char **ccname)
+{
+ krb5_error_code ret = ENOMEM;
+ krb5_ccache cc = NULL;
+ time_t life;
+ char *s = NULL;
+
+ *ccname = NULL;
+
+ /*
+ * Name the ccache after the principal. The principal may have special
+ * characters in it, such as / or \ (path component separarot), or shell
+ * special characters, so princ_fs_encode() it to make a ccache name.
+ */
+ if ((s = princ_fs_encode(princ)) == NULL ||
+ asprintf(ccname, "FILE:%s/%s.cc", cache_dir, s) == -1 ||
+ *ccname == NULL) {
+ free(s);
+ return ENOMEM;
+ }
+ free(s);
+
+ if ((ret = krb5_cc_resolve(context, *ccname, &cc))) {
+ /* krb5_cc_resolve() suceeds even if the file doesn't exist */
+ free(*ccname);
+ *ccname = NULL;
+ cc = NULL;
+ }
+
+ /* Check if we have a good enough credential */
+ if (ret == 0 &&
+ (ret = krb5_cc_get_lifetime(context, cc, &life)) == 0 && life > 60) {
+ krb5_cc_close(context, cc);
+ return 0;
+ }
+ if (cc)
+ krb5_cc_close(context, cc);
+ return ret ? ret : ENOENT;
+}
+
+static krb5_error_code
+get_ccache(struct bx509_request_desc *r, krb5_ccache *cc, int *won)
+{
+ krb5_error_code ret = 0;
+ char *temp_ccname = NULL;
+ const char *fn = NULL;
+ time_t life;
+ int fd = -1;
+
+ /*
+ * Open and lock a .new ccache file. Use .new to avoid garbage files on
+ * crash.
+ *
+ * We can race with other threads to do this, so we loop until we
+ * definitively win or definitely lose the race. We win when we have a) an
+ * open FD that is b) flock'ed, and c) we observe with lstat() that the
+ * file we opened and locked is the same as on disk after locking.
+ *
+ * We don't close the FD until we're done.
+ *
+ * If we had a proper anon MEMORY ccache, we could instead use that for a
+ * temporary ccache, and then the initialization of and move to the final
+ * FILE ccache would take care to mkstemp() and rename() into place.
+ * fcc_open() basically does a similar thing.
+ */
+ *cc = NULL;
+ *won = -1;
+ if (asprintf(&temp_ccname, "%s.ccnew", r->ccname) == -1 ||
+ temp_ccname == NULL)
+ ret = ENOMEM;
+ if (ret == 0)
+ fn = temp_ccname + sizeof("FILE:") - 1;
+ if (ret == 0) do {
+ struct stat st1, st2;
+ /*
+ * Open and flock the temp ccache file.
+ *
+ * XXX We should really a) use _krb5_xlock(), or move that into
+ * lib/roken anyways, b) abstract this loop into a utility function in
+ * lib/roken.
+ */
+ if (fd != -1) {
+ (void) close(fd);
+ fd = -1;
+ }
+ errno = 0;
+ memset(&st1, 0, sizeof(st1));
+ memset(&st2, 0xff, sizeof(st2));
+ if (ret == 0 &&
+ ((fd = open(fn, O_RDWR | O_CREAT, 0600)) == -1 ||
+ flock(fd, LOCK_EX) == -1 ||
+ (lstat(fn, &st1) == -1 && errno != ENOENT) ||
+ fstat(fd, &st2) == -1))
+ ret = errno;
+ if (ret == 0 && errno == 0 &&
+ st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino) {
+ if (S_ISREG(st1.st_mode))
+ break;
+ if (unlink(fn) == -1)
+ ret = errno;
+ }
+ } while (ret == 0);
+
+ /* Check if we lost any race to acquire Kerberos creds */
+ if (ret == 0)
+ ret = krb5_cc_resolve(r->context, temp_ccname, cc);
+ if (ret == 0) {
+ ret = krb5_cc_get_lifetime(r->context, *cc, &life);
+ if (ret == 0 && life > 60)
+ *won = 0; /* We lost the race, but we win: we get to do less work */
+ *won = 1;
+ ret = 0;
+ }
+ free(temp_ccname);
+ if (fd != -1)
+ (void) close(fd); /* Drops the flock */
+ return ret;
+}
+
+/*
+ * Acquire credentials for `princ' using PKINIT and the PKIX credentials in
+ * `pkix_store', then place the result in the ccache named `ccname' (which will
+ * be in our own private `cache_dir').
+ *
+ * XXX This function could be rewritten using gss_acquire_cred_from() and
+ * gss_store_cred_into() provided we add new generic cred store key/value pairs
+ * for PKINIT.
+ */
+static krb5_error_code
+do_pkinit(struct bx509_request_desc *r, enum k5_creds_kind kind)
+{
+ krb5_get_init_creds_opt *opt = NULL;
+ krb5_init_creds_context ctx = NULL;
+ krb5_error_code ret = 0;
+ krb5_ccache temp_cc = NULL;
+ krb5_ccache cc = NULL;
+ krb5_principal p = NULL;
+ const char *crealm;
+ const char *cname = r->for_cname ? r->for_cname : r->cname;
+
+ if (kind == K5_CREDS_CACHED) {
+ int won = -1;
+
+ ret = get_ccache(r, &temp_cc, &won);
+ if (ret || !won)
+ goto out;
+ /*
+ * We won the race to do PKINIT. Setup to acquire Kerberos creds with
+ * PKINIT.
+ *
+ * We should really make sure that gss_acquire_cred_from() can do this
+ * for us. We'd add generic cred store key/value pairs for PKIX cred
+ * store, trust anchors, and so on, and acquire that way, then
+ * gss_store_cred_into() to save it in a FILE ccache.
+ */
+ } else {
+ ret = krb5_cc_new_unique(r->context, "FILE", NULL, &temp_cc);
+ }
+
+ if (ret == 0)
+ ret = krb5_parse_name(r->context, cname, &p);
+ if (ret == 0)
+ crealm = krb5_principal_get_realm(r->context, p);
+ if (ret == 0)
+ ret = krb5_get_init_creds_opt_alloc(r->context, &opt);
+ if (ret == 0)
+ krb5_get_init_creds_opt_set_default_flags(r->context, "kinit", crealm,
+ opt);
+ if (ret == 0 && kind == K5_CREDS_EPHEMERAL &&
+ !krb5_config_get_bool_default(r->context, NULL, TRUE,
+ "get-tgt", "no_addresses", NULL)) {
+ krb5_addresses addr;
+
+ ret = _krb5_parse_address_no_lookup(r->context, r->frombuf, &addr);
+ if (ret == 0)
+ ret = krb5_append_addresses(r->context, &r->tgt_addresses,
+ &addr);
+ }
+ if (ret == 0) {
+ if (r->tgt_addresses.len == 0)
+ ret = krb5_get_init_creds_opt_set_addressless(r->context, opt, 1);
+ else
+ krb5_get_init_creds_opt_set_address_list(opt, &r->tgt_addresses);
+ }
+ if (ret == 0)
+ ret = krb5_get_init_creds_opt_set_pkinit(r->context, opt, p,
+ r->pkix_store,
+ NULL, /* pkinit_anchor */
+ NULL, /* anchor_chain */
+ NULL, /* pkinit_crl */
+ 0, /* flags */
+ NULL, /* prompter */
+ NULL, /* prompter data */
+ NULL /* password */);
+ if (ret == 0)
+ ret = krb5_init_creds_init(r->context, p,
+ NULL /* prompter */,
+ NULL /* prompter data */,
+ 0 /* start_time */,
+ opt, &ctx);
+
+ /*
+ * Finally, do the AS exchange w/ PKINIT, extract the new Kerberos creds
+ * into temp_cc, and rename into place. Note that krb5_cc_move() closes
+ * the source ccache, so we set temp_cc = NULL if it succeeds.
+ */
+ if (ret == 0)
+ ret = krb5_init_creds_get(r->context, ctx);
+ if (ret == 0)
+ ret = krb5_init_creds_store(r->context, ctx, temp_cc);
+ if (kind == K5_CREDS_CACHED) {
+ if (ret == 0)
+ ret = krb5_cc_resolve(r->context, r->ccname, &cc);
+ if (ret == 0)
+ ret = krb5_cc_move(r->context, temp_cc, cc);
+ if (ret == 0)
+ temp_cc = NULL;
+ } else if (ret == 0 && kind == K5_CREDS_EPHEMERAL) {
+ ret = krb5_cc_get_full_name(r->context, temp_cc, &r->ccname);
+ }
+
+out:
+ if (ctx)
+ krb5_init_creds_free(r->context, ctx);
+ krb5_get_init_creds_opt_free(r->context, opt);
+ krb5_free_principal(r->context, p);
+ krb5_cc_close(r->context, temp_cc);
+ krb5_cc_close(r->context, cc);
+ return ret;
+}
+
+static krb5_error_code
+load_priv_key(krb5_context context, const char *fn, hx509_private_key *key)
+{
+ hx509_private_key *keys = NULL;
+ krb5_error_code ret;
+ hx509_certs certs = NULL;
+
+ *key = NULL;
+ ret = hx509_certs_init(context->hx509ctx, fn, 0, NULL, &certs);
+ if (ret == ENOENT)
+ return 0;
+ if (ret == 0)
+ ret = _hx509_certs_keys_get(context->hx509ctx, certs, &keys);
+ if (ret == 0 && keys[0] == NULL)
+ ret = ENOENT; /* XXX Better error please */
+ if (ret == 0)
+ *key = _hx509_private_key_ref(keys[0]);
+ if (ret)
+ krb5_set_error_message(context, ret, "Could not load private "
+ "impersonation key from %s for PKINIT: %s", fn,
+ hx509_get_error_string(context->hx509ctx, ret));
+ _hx509_certs_keys_free(context->hx509ctx, keys);
+ hx509_certs_free(&certs);
+ return ret;
+}
+
+static krb5_error_code
+k5_do_CA(struct bx509_request_desc *r)
+{
+ SubjectPublicKeyInfo spki;
+ hx509_private_key key = NULL;
+ krb5_error_code ret = 0;
+ krb5_principal p = NULL;
+ hx509_request req = NULL;
+ hx509_certs certs = NULL;
+ KeyUsage ku = int2KeyUsage(0);
+ const char *cname = r->for_cname ? r->for_cname : r->cname;
+
+ memset(&spki, 0, sizeof(spki));
+ ku.digitalSignature = 1;
+
+ /* Make a CSR (halfway -- we don't need to sign it here) */
+ /* XXX Load impersonation key just once?? */
+ ret = load_priv_key(r->context, impersonation_key_fn, &key);
+ if (ret == 0)
+ ret = hx509_request_init(r->context->hx509ctx, &req);
+ if (ret == 0)
+ ret = krb5_parse_name(r->context, cname, &p);
+ if (ret == 0)
+ ret = hx509_private_key2SPKI(r->context->hx509ctx, key, &spki);
+ if (ret == 0)
+ hx509_request_set_SubjectPublicKeyInfo(r->context->hx509ctx, req,
+ &spki);
+ free_SubjectPublicKeyInfo(&spki);
+ if (ret == 0)
+ ret = hx509_request_add_pkinit(r->context->hx509ctx, req, cname);
+ if (ret == 0)
+ ret = hx509_request_add_eku(r->context->hx509ctx, req,
+ &asn1_oid_id_pkekuoid);
+
+ /* Mark it authorized */
+ if (ret == 0)
+ ret = hx509_request_authorize_san(req, 0);
+ if (ret == 0)
+ ret = hx509_request_authorize_eku(req, 0);
+ if (ret == 0)
+ hx509_request_authorize_ku(req, ku);
+
+ /* Issue the certificate */
+ if (ret == 0)
+ ret = kdc_issue_certificate(r->context, "get-tgt", logfac, req, p,
+ &r->token_times, r->req_life,
+ 1 /* send_chain */, &certs);
+ krb5_free_principal(r->context, p);
+ hx509_request_free(&req);
+ p = NULL;
+
+ if (ret == KRB5KDC_ERR_POLICY || ret == EACCES) {
+ hx509_private_key_free(&key);
+ return bad_403(r, ret,
+ "Certificate request denied for policy reasons");
+ }
+ if (ret == ENOMEM) {
+ hx509_private_key_free(&key);
+ return bad_503(r, ret, "Certificate issuance failed");
+ }
+ if (ret) {
+ hx509_private_key_free(&key);
+ return bad_500(r, ret, "Certificate issuance failed");
+ }
+
+ /* Setup PKIX store and extract the certificate chain into it */
+ ret = mk_pkix_store(&r->pkix_store);
+ if (ret == 0)
+ ret = store_certs(r->context->hx509ctx, r->pkix_store, certs, key);
+ hx509_private_key_free(&key);
+ hx509_certs_free(&certs);
+ if (ret)
+ return bad_500(r, ret,
+ "Could not create PEM store for issued certificate");
+ return 0;
+}
+
+/* Get impersonated Kerberos credentials for `cprinc' */
+static krb5_error_code
+k5_get_creds(struct bx509_request_desc *r, enum k5_creds_kind kind)
+{
+ krb5_error_code ret;
+ const char *cname = r->for_cname ? r->for_cname : r->cname;
+
+ /* If we have a live ccache for `cprinc', we're done */
+ r->cckind = kind;
+ if (kind == K5_CREDS_CACHED &&
+ (ret = find_ccache(r->context, cname, &r->ccname)) == 0)
+ return ret; /* Success */
+
+ /*
+ * Else we have to acquire a credential for them using their bearer token
+ * for authentication (and our keytab / initiator credentials perhaps).
+ */
+ if ((ret = k5_do_CA(r)))
+ return ret; /* k5_do_CA() calls bad_req() */
+
+ if (ret == 0)
+ ret = do_pkinit(r, kind);
+ return ret;
+}
+
+/* Accumulate strings */
+static void
+acc_str(char **acc, char *adds, size_t addslen)
+{
+ char *tmp = NULL;
+ int l = addslen <= INT_MAX ? (int)addslen : INT_MAX;
+
+ if (asprintf(&tmp, "%s%s%.*s",
+ *acc ? *acc : "",
+ *acc ? "; " : "", l, adds) > -1 &&
+ tmp) {
+ free(*acc);
+ *acc = tmp;
+ }
+}
+
+static char *
+fmt_gss_error(OM_uint32 code, gss_OID mech)
+{
+ gss_buffer_desc buf;
+ OM_uint32 major, minor;
+ OM_uint32 type = mech == GSS_C_NO_OID ? GSS_C_GSS_CODE: GSS_C_MECH_CODE;
+ OM_uint32 more = 0;
+ char *r = NULL;
+
+ do {
+ major = gss_display_status(&minor, code, type, mech, &more, &buf);
+ if (!GSS_ERROR(major))
+ acc_str(&r, (char *)buf.value, buf.length);
+ gss_release_buffer(&minor, &buf);
+ } while (!GSS_ERROR(major) && more);
+ return r;
+}
+
+static char *
+fmt_gss_errors(const char *r, OM_uint32 major, OM_uint32 minor, gss_OID mech)
+{
+ char *ma, *mi, *s;
+
+ ma = fmt_gss_error(major, GSS_C_NO_OID);
+ mi = mech == GSS_C_NO_OID ? NULL : fmt_gss_error(minor, mech);
+ if (asprintf(&s, "%s: %s%s%s", r,
+ ma ? ma : "Out of memory",
+ mi ? ": " : "",
+ mi ? mi : "") > -1 &&
+ s) {
+ free(ma);
+ free(mi);
+ return s;
+ }
+ free(mi);
+ return ma;
+}
+
+/* GSS-API error */
+static krb5_error_code
+bad_req_gss(struct bx509_request_desc *r,
+ OM_uint32 major,
+ OM_uint32 minor,
+ gss_OID mech,
+ int http_status_code,
+ const char *reason)
+{
+ krb5_error_code ret;
+ char *msg = fmt_gss_errors(reason, major, minor, mech);
+
+ if (major == GSS_S_BAD_NAME || major == GSS_S_BAD_NAMETYPE)
+ http_status_code = MHD_HTTP_BAD_REQUEST;
+
+ if (msg)
+ ret = resp(r, http_status_code, MHD_RESPMEM_MUST_COPY, NULL,
+ msg, strlen(msg), NULL);
+ else
+ ret = resp(r, http_status_code, MHD_RESPMEM_MUST_COPY, NULL,
+ "Out of memory while formatting GSS error message",
+ sizeof("Out of memory while formatting GSS error message") - 1, NULL);
+ free(msg);
+ return ret;
+}
+
+/* Make an HTTP/Negotiate token */
+static krb5_error_code
+mk_nego_tok(struct bx509_request_desc *r,
+ char **nego_tok,
+ size_t *nego_toksz)
+{
+ gss_key_value_element_desc kv[1] = { { "ccache", r->ccname } };
+ gss_key_value_set_desc store = { 1, kv };
+ gss_buffer_desc token = GSS_C_EMPTY_BUFFER;
+ gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
+ gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
+ gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
+ gss_name_t iname = GSS_C_NO_NAME;
+ gss_name_t aname = GSS_C_NO_NAME;
+ OM_uint32 major, minor, junk;
+ krb5_error_code ret; /* More like a system error code here */
+ const char *cname = r->for_cname ? r->for_cname : r->cname;
+ char *token_b64 = NULL;
+
+ *nego_tok = NULL;
+ *nego_toksz = 0;
+
+ /* Import initiator name */
+ name.length = strlen(cname);
+ name.value = rk_UNCONST(cname);
+ major = gss_import_name(&minor, &name, GSS_KRB5_NT_PRINCIPAL_NAME, &iname);
+ if (major != GSS_S_COMPLETE)
+ return bad_req_gss(r, major, minor, GSS_C_NO_OID,
+ MHD_HTTP_SERVICE_UNAVAILABLE,
+ "Could not import cprinc parameter value as "
+ "Kerberos principal name");
+
+ /* Import target acceptor name */
+ name.length = strlen(r->target);
+ name.value = rk_UNCONST(r->target);
+ major = gss_import_name(&minor, &name, GSS_C_NT_HOSTBASED_SERVICE, &aname);
+ if (major != GSS_S_COMPLETE) {
+ (void) gss_release_name(&junk, &iname);
+ return bad_req_gss(r, major, minor, GSS_C_NO_OID,
+ MHD_HTTP_SERVICE_UNAVAILABLE,
+ "Could not import target parameter value as "
+ "Kerberos principal name");
+ }
+
+ /* Acquire a credential from the given ccache */
+ major = gss_add_cred_from(&minor, cred, iname, GSS_KRB5_MECHANISM,
+ GSS_C_INITIATE, GSS_C_INDEFINITE, 0, &store,
+ &cred, NULL, NULL, NULL);
+ (void) gss_release_name(&junk, &iname);
+ if (major != GSS_S_COMPLETE) {
+ (void) gss_release_name(&junk, &aname);
+ return bad_req_gss(r, major, minor, GSS_KRB5_MECHANISM,
+ MHD_HTTP_FORBIDDEN, "Could not acquire credentials "
+ "for requested cprinc");
+ }
+
+ major = gss_init_sec_context(&minor, cred, &ctx, aname,
+ GSS_KRB5_MECHANISM, 0, GSS_C_INDEFINITE,
+ NULL, GSS_C_NO_BUFFER, NULL, &token, NULL,
+ NULL);
+ (void) gss_delete_sec_context(&junk, &ctx, GSS_C_NO_BUFFER);
+ (void) gss_release_name(&junk, &aname);
+ (void) gss_release_cred(&junk, &cred);
+ if (major != GSS_S_COMPLETE)
+ return bad_req_gss(r, major, minor, GSS_KRB5_MECHANISM,
+ MHD_HTTP_SERVICE_UNAVAILABLE, "Could not acquire "
+ "Negotiate token for requested target");
+
+ /* Encode token, output */
+ ret = rk_base64_encode(token.value, token.length, &token_b64);
+ (void) gss_release_buffer(&junk, &token);
+ if (ret > 0)
+ ret = asprintf(nego_tok, "Negotiate %s", token_b64);
+ free(token_b64);
+ if (ret < 0 || *nego_tok == NULL)
+ return bad_req(r, errno, MHD_HTTP_SERVICE_UNAVAILABLE,
+ "Could not allocate memory for encoding Negotiate "
+ "token");
+ *nego_toksz = ret;
+ return 0;
+}
+
+static krb5_error_code
+bnegotiate_get_target(struct bx509_request_desc *r)
+{
+ const char *target;
+ const char *redir;
+ const char *referer; /* misspelled on the wire, misspelled here, FYI */
+ const char *authority;
+ const char *local_part;
+ char *s1 = NULL;
+ char *s2 = NULL;
+
+ target = MHD_lookup_connection_value(r->connection, MHD_GET_ARGUMENT_KIND,
+ "target");
+ redir = MHD_lookup_connection_value(r->connection, MHD_GET_ARGUMENT_KIND,
+ "redirect");
+ referer = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_REFERER);
+ if (target != NULL && redir == NULL) {
+ r->target = target;
+ return 0;
+ }
+ if (target == NULL && redir == NULL)
+ return bad_400(r, EINVAL,
+ "Query missing 'target' or 'redirect' parameter value");
+ if (target != NULL && redir != NULL)
+ return bad_403(r, EACCES,
+ "Only one of 'target' or 'redirect' parameter allowed");
+ if (redir != NULL && referer == NULL)
+ return bad_403(r, EACCES,
+ "Redirect request without Referer header nor allowed");
+
+ if (strncmp(referer, "https://", sizeof("https://") - 1) != 0 ||
+ strncmp(redir, "https://", sizeof("https://") - 1) != 0)
+ return bad_403(r, EACCES,
+ "Redirect requests permitted only for https referrers");
+
+ /* Parse out authority from each URI, redirect and referrer */
+ authority = redir + sizeof("https://") - 1;
+ if ((local_part = strchr(authority, '/')) == NULL)
+ local_part = authority + strlen(authority);
+ if ((s1 = strndup(authority, local_part - authority)) == NULL)
+ return bad_enomem(r, ENOMEM);
+
+ authority = referer + sizeof("https://") - 1;
+ if ((local_part = strchr(authority, '/')) == NULL)
+ local_part = authority + strlen(authority);
+ if ((s2 = strndup(authority, local_part - authority)) == NULL) {
+ free(s1);
+ return bad_enomem(r, ENOMEM);
+ }
+
+ /* Both must match */
+ if (strcasecmp(s1, s2) != 0) {
+ free(s2);
+ free(s1);
+ return bad_403(r, EACCES, "Redirect request does not match referer");
+ }
+ free(s2);
+
+ if (strchr(s1, '@')) {
+ free(s1);
+ return bad_403(r, EACCES,
+ "Redirect request authority has login information");
+ }
+
+ /* Extract hostname portion of authority and format GSS name */
+ if (strchr(s1, ':'))
+ *strchr(s1, ':') = '\0';
+ if (asprintf(&r->freeme1, "HTTP@%s", s1) == -1 || r->freeme1 == NULL) {
+ free(s1);
+ return bad_enomem(r, ENOMEM);
+ }
+
+ r->target = r->freeme1;
+ r->redir = redir;
+ free(s1);
+ return 0;
+}
+
+/*
+ * Implements /bnegotiate end-point.
+ *
+ * Query parameters (mutually exclusive):
+ *
+ * - target=<name>
+ * - redirect=<URL-encoded-URL>
+ *
+ * If the redirect query parameter is set then the Referer: header must be as
+ * well, and the authority of the redirect and Referer URIs must be the same.
+ */
+static krb5_error_code
+bnegotiate(struct bx509_request_desc *r)
+{
+ krb5_error_code ret;
+ size_t nego_toksz = 0;
+ char *nego_tok = NULL;
+
+ ret = bnegotiate_get_target(r);
+ if (ret)
+ return ret; /* bnegotiate_get_target() calls bad_req() */
+ heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS, "target", "%s",
+ r->target ? r->target : "<unknown>");
+ heim_audit_setkv_bool((heim_svc_req_desc)r, "redir", !!r->redir);
+
+ /*
+ * Make sure we have Kerberos credentials for cprinc. If we have them
+ * cached from earlier, this will be fast (all local), else it will involve
+ * taking a file lock and talking to the KDC using kx509 and PKINIT.
+ *
+ * Perhaps we could use S4U instead, which would speed up the slow path a
+ * bit.
+ */
+ ret = k5_get_creds(r, K5_CREDS_CACHED);
+ if (ret)
+ return bad_403(r, ret,
+ "Could not acquire Kerberos credentials using PKINIT");
+
+ /* Acquire the Negotiate token and output it */
+ if (ret == 0 && r->ccname != NULL)
+ ret = mk_nego_tok(r, &nego_tok, &nego_toksz);
+
+ if (ret == 0) {
+ /* Look ma', Negotiate as an OAuth-like token system! */
+ if (r->redir)
+ ret = resp(r, MHD_HTTP_TEMPORARY_REDIRECT, MHD_RESPMEM_PERSISTENT,
+ NULL, "", 0, nego_tok);
+ else
+ ret = resp(r, MHD_HTTP_OK, MHD_RESPMEM_MUST_COPY,
+ "application/x-negotiate-token", nego_tok, nego_toksz,
+ NULL);
+ }
+
+ free(nego_tok);
+ return ret;
+}
+
+static krb5_error_code
+authorize_TGT_REQ(struct bx509_request_desc *r)
+{
+ krb5_principal p = NULL;
+ krb5_error_code ret;
+ const char *for_cname = r->for_cname ? r->for_cname : r->cname;
+
+ if (for_cname == r->cname || strcmp(r->cname, r->for_cname) == 0)
+ return 0;
+
+ ret = hx509_request_init(r->context->hx509ctx, &r->req);
+ if (ret)
+ return bad_500(r, ret, "Out of resources");
+ heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
+ "requested_krb5PrincipalName", "%s", for_cname);
+ ret = hx509_request_add_eku(r->context->hx509ctx, r->req,
+ ASN1_OID_ID_PKEKUOID);
+ if (ret == 0)
+ ret = hx509_request_add_pkinit(r->context->hx509ctx, r->req,
+ for_cname);
+ if (ret == 0)
+ ret = krb5_parse_name(r->context, r->cname, &p);
+ if (ret == 0)
+ ret = kdc_authorize_csr(r->context, "get-tgt", r->req, p);
+ krb5_free_principal(r->context, p);
+ hx509_request_free(&r->req);
+ r->req = NULL;
+ if (ret)
+ return bad_403(r, ret, "Not authorized to requested TGT");
+ return ret;
+}
+
+static heim_mhd_result
+get_tgt_param_cb(void *d,
+ enum MHD_ValueKind kind,
+ const char *key,
+ const char *val)
+{
+ struct bx509_request_desc *r = d;
+
+ if (strcmp(key, "address") == 0 && val) {
+ if (!krb5_config_get_bool_default(r->context, NULL,
+ FALSE,
+ "get-tgt", "allow_addresses", NULL)) {
+ krb5_set_error_message(r->context, r->error_code = ENOTSUP,
+ "Query parameter %s not allowed", key);
+ } else {
+ krb5_addresses addresses;
+
+ r->error_code = _krb5_parse_address_no_lookup(r->context, val,
+ &addresses);
+ if (r->error_code == 0)
+ r->error_code = krb5_append_addresses(r->context, &r->tgt_addresses,
+ &addresses);
+ krb5_free_addresses(r->context, &addresses);
+ }
+ } else if (strcmp(key, "cname") == 0) {
+ /* Handled upstairs */
+ ;
+ } else if (strcmp(key, "lifetime") == 0 && val) {
+ r->req_life = parse_time(val, "day");
+ } else {
+ /* Produce error for unknown params */
+ heim_audit_setkv_bool((heim_svc_req_desc)r, "requested_unknown", TRUE);
+ krb5_set_error_message(r->context, r->error_code = ENOTSUP,
+ "Query parameter %s not supported", key);
+ }
+ return r->error_code == 0 ? MHD_YES : MHD_NO /* Stop iterating */;
+}
+
+/*
+ * Implements /get-tgt end-point.
+ *
+ * Query parameters:
+ *
+ * - cname=<name> (client principal name, if not the same as the authenticated
+ * name, then this will be impersonated if allowed; may be
+ * given only once)
+ *
+ * - address=<IP> (IP address to add as a ticket address; may be given
+ * multiple times)
+ *
+ * - lifetime=<time> (requested lifetime for the ticket; may be given only
+ * once)
+ */
+static krb5_error_code
+get_tgt(struct bx509_request_desc *r)
+{
+ krb5_error_code ret;
+ size_t bodylen;
+ const char *fn;
+ void *body;
+
+ r->for_cname = MHD_lookup_connection_value(r->connection,
+ MHD_GET_ARGUMENT_KIND, "cname");
+ if (r->for_cname && r->for_cname[0] == '\0')
+ r->for_cname = NULL;
+ ret = authorize_TGT_REQ(r);
+ if (ret)
+ return ret; /* authorize_TGT_REQ() calls bad_req() */
+
+ r->error_code = 0;
+ (void) MHD_get_connection_values(r->connection, MHD_GET_ARGUMENT_KIND,
+ get_tgt_param_cb, r);
+ ret = r->error_code;
+
+ /* k5_get_creds() calls bad_req() */
+ if (ret == 0)
+ ret = k5_get_creds(r, K5_CREDS_EPHEMERAL);
+ if (ret)
+ return bad_403(r, ret,
+ "Could not acquire Kerberos credentials using PKINIT");
+
+ fn = strchr(r->ccname, ':');
+ if (fn == NULL)
+ return bad_500(r, ret, "Impossible error");
+ fn++;
+ if ((errno = rk_undumpdata(fn, &body, &bodylen)))
+ return bad_503(r, ret, "Could not get TGT");
+
+ ret = resp(r, MHD_HTTP_OK, MHD_RESPMEM_MUST_COPY,
+ "application/x-krb5-ccache", body, bodylen, NULL);
+ free(body);
+ return ret;
+}
+
+static int
+get_tgts_accumulate_ccache_write_json(struct bx509_request_desc *r,
+ krb5_error_code code,
+ const char *data,
+ size_t datalen)
+{
+ heim_object_t k, v;
+ heim_string_t text;
+ heim_error_t e = NULL;
+ heim_dict_t o;
+ int ret;
+
+ o = heim_dict_create(9);
+ k = heim_string_create("name");
+ v = heim_string_create(r->for_cname);
+ if (o && k && v)
+ ret = heim_dict_set_value(o, k, v);
+ else
+ ret = ENOMEM;
+
+ if (ret == 0) {
+ heim_release(v);
+ heim_release(k);
+ k = heim_string_create("error_code");
+ v = heim_number_create(code);
+ if (k && v)
+ ret = heim_dict_set_value(o, k, v);
+ }
+ if (ret == 0 && data != NULL) {
+ heim_release(v);
+ heim_release(k);
+ k = heim_string_create("ccache");
+ v = heim_data_create(data, datalen);
+ if (k && v)
+ ret = heim_dict_set_value(o, k, v);
+ }
+ if (ret == 0 && code != 0) {
+ const char *s = krb5_get_error_message(r->context, code);
+
+ heim_release(v);
+ heim_release(k);
+ k = heim_string_create("error");
+ v = heim_string_create(s ? s : "Out of memory");
+ krb5_free_error_message(r->context, s);
+ if (k && v)
+ ret = heim_dict_set_value(o, k, v);
+ }
+ heim_release(v);
+ heim_release(k);
+ if (ret) {
+ heim_release(o);
+ return bad_503(r, errno, "Out of memory");
+ }
+
+ text = heim_json_copy_serialize(o,
+ HEIM_JSON_F_NO_DATA_DICT |
+ HEIM_JSON_F_ONE_LINE,
+ &e);
+ if (text) {
+ const char *s = heim_string_get_utf8(text);
+
+ (void) fwrite(s, strlen(s), 1, r->tgts);
+ } else {
+ const char *s = NULL;
+ v = heim_error_copy_string(e);
+ if (v)
+ s = heim_string_get_utf8(v);
+ if (s == NULL)
+ s = "<unknown encoder error>";
+ krb5_log_msg(r->context, logfac, 1, NULL, "Failed to encode JSON text with ccache or error for %s: %s",
+ r->for_cname, s);
+ heim_release(v);
+ }
+ heim_release(text);
+ heim_release(o);
+ return MHD_YES;
+}
+
+/* Writes one ccache to a response file, as JSON */
+static int
+get_tgts_accumulate_ccache(struct bx509_request_desc *r, krb5_error_code ret)
+{
+ const char *fn;
+ size_t bodylen = 0;
+ void *body = NULL;
+ int res;
+
+ if (r->tgts == NULL) {
+ int fd = -1;
+
+ if (asprintf(&r->tgts_filename,
+ "%s/tgts-json-XXXXXX", cache_dir) == -1 ||
+ r->tgts_filename == NULL) {
+ free(r->tgts_filename);
+ r->tgts_filename = NULL;
+
+ return bad_enomem(r, r->error_code = ENOMEM);
+ }
+ if ((fd = mkstemp(r->tgts_filename)) == -1)
+ return bad_req(r, errno, MHD_HTTP_SERVICE_UNAVAILABLE,
+ "%s", strerror(r->error_code = errno));
+ if ((r->tgts = fdopen(fd, "w+")) == NULL) {
+ (void) close(fd);
+ return bad_req(r, errno, MHD_HTTP_SERVICE_UNAVAILABLE,
+ "%s", strerror(r->error_code = errno));
+ }
+ }
+
+ if (ret == 0) {
+ fn = strchr(r->ccname, ':');
+ if (fn == NULL)
+ return bad_req(r, errno, MHD_HTTP_SERVICE_UNAVAILABLE,
+ "Internal error (invalid credentials cache name)");
+ fn++;
+ if ((r->error_code = rk_undumpdata(fn, &body, &bodylen)))
+ return bad_req(r, errno, MHD_HTTP_SERVICE_UNAVAILABLE,
+ "%s", strerror(r->error_code));
+ (void) unlink(fn);
+ free(r->ccname);
+ r->ccname = NULL;
+ if (bodylen > INT_MAX >> 4) {
+ free(body);
+ return bad_req(r, errno, MHD_HTTP_SERVICE_UNAVAILABLE,
+ "Credentials cache too large!");
+ }
+ }
+
+ res = get_tgts_accumulate_ccache_write_json(r, ret, body, bodylen);
+ free(body);
+ return res;
+}
+
+static heim_mhd_result
+get_tgts_param_authorize_cb(void *d,
+ enum MHD_ValueKind kind,
+ const char *key,
+ const char *val)
+{
+ struct bx509_request_desc *r = d;
+ krb5_error_code ret = 0;
+
+ if (strcmp(key, "cname") != 0 || val == NULL)
+ return MHD_YES;
+
+ if (r->req == NULL) {
+ ret = hx509_request_init(r->context->hx509ctx, &r->req);
+ if (ret == 0)
+ ret = hx509_request_add_eku(r->context->hx509ctx, r->req,
+ ASN1_OID_ID_PKEKUOID);
+ if (ret)
+ return bad_500(r, ret, "Out of resources");
+ }
+ heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
+ "requested_krb5PrincipalName", "%s", val);
+ ret = hx509_request_add_pkinit(r->context->hx509ctx, r->req,
+ val);
+ if (ret)
+ return bad_403(r, ret, "Not authorized to requested TGT");
+ return MHD_YES;
+}
+
+/* For each requested principal, produce a ccache */
+static heim_mhd_result
+get_tgts_param_execute_cb(void *d,
+ enum MHD_ValueKind kind,
+ const char *key,
+ const char *val)
+{
+ struct bx509_request_desc *r = d;
+ hx509_san_type san_type;
+ krb5_error_code ret;
+ size_t san_idx = r->san_idx++;
+ const char *save_for_cname = r->for_cname;
+ char *s = NULL;
+ int res;
+
+ /* We expect only cname=principal q-params here */
+ if (strcmp(key, "cname") != 0 || val == NULL)
+ return MHD_YES;
+
+ /*
+ * We expect the `san_idx'th SAN in the `r->req' request checked by
+ * kdc_authorize_csr() to be the same as this cname. This happens
+ * naturally because we add these SANs to `r->req' in the same order as we
+ * visit them here (unless our HTTP library somehow went crazy).
+ *
+ * Still, we check that it's the same SAN.
+ */
+ ret = hx509_request_get_san(r->req, san_idx, &san_type, &s);
+ if (ret == HX509_NO_ITEM ||
+ san_type != HX509_SAN_TYPE_PKINIT ||
+ strcmp(s, val) != 0) {
+ /*
+ * If the cname and SAN don't match, it's some weird internal error
+ * (can't happen).
+ */
+ krb5_set_error_message(r->context, r->error_code = EACCES,
+ "PKINIT SAN not granted: %s (internal error)",
+ val);
+ ret = EACCES;
+ }
+
+ /*
+ * We're going to pretend to be this SAN for the purpose of acquring a TGT
+ * for it. So we "push" `r->for_cname'.
+ */
+ if (ret == 0)
+ r->for_cname = val;
+
+ /*
+ * Our authorizer supports partial authorization where the whole request is
+ * rejected but some features of it are permitted.
+ *
+ * (In most end-points we don't want partial authorization, but in
+ * /get-tgts we very much do.)
+ */
+ if (ret == 0 && !hx509_request_san_authorized_p(r->req, san_idx)) {
+ heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
+ "REJECT_krb5PrincipalName", "%s", val);
+ krb5_set_error_message(r->context, r->error_code = EACCES,
+ "PKINIT SAN denied: %s", val);
+ ret = EACCES;
+ }
+ if (ret == 0) {
+ heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
+ "ACCEPT_krb5PrincipalName", "%s", val);
+ ret = k5_get_creds(r, K5_CREDS_EPHEMERAL);
+ if (ret == 0)
+ heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
+ "ISSUE_krb5PrincipalName", "%s", val);
+ }
+
+ /*
+ * If ret == 0 this will gather the TGT we acquired, else it will acquire
+ * the error we got.
+ */
+ res = get_tgts_accumulate_ccache(r, ret);
+
+ /* Now we "pop" `r->for_cname' */
+ r->for_cname = save_for_cname;
+
+ hx509_xfree(s);
+ return res;
+}
+
+/*
+ * Implements /get-tgts end-point.
+ *
+ * Query parameters:
+ *
+ * - cname=<name> (client principal name, if not the same as the authenticated
+ * name, then this will be impersonated if allowed; may be
+ * given multiple times)
+ */
+static krb5_error_code
+get_tgts(struct bx509_request_desc *r)
+{
+ krb5_error_code ret;
+ krb5_principal p = NULL;
+ size_t bodylen;
+ void *body;
+ int res = MHD_YES;
+
+ /* Prep to authorize */
+ ret = krb5_parse_name(r->context, r->cname, &p);
+ if (ret)
+ return bad_403(r, ret, "Could not parse caller principal name");
+ if (ret == 0) {
+ /* Extract q-params other than `cname' */
+ r->error_code = 0;
+ res = MHD_get_connection_values(r->connection, MHD_GET_ARGUMENT_KIND,
+ get_tgt_param_cb, r);
+ if (r->response || res == MHD_NO) {
+ krb5_free_principal(r->context, p);
+ return res;
+ }
+
+ ret = r->error_code;
+ }
+ if (ret == 0) {
+ /*
+ * Check authorization of the authenticated client to the requested
+ * client principal names (calls bad_req()).
+ */
+ r->error_code = 0;
+ res = MHD_get_connection_values(r->connection, MHD_GET_ARGUMENT_KIND,
+ get_tgts_param_authorize_cb, r);
+ if (r->response || res == MHD_NO) {
+ krb5_free_principal(r->context, p);
+ return res;
+ }
+
+ ret = r->error_code;
+ if (ret == 0) {
+ /* Use the same configuration as /get-tgt (or should we?) */
+ ret = kdc_authorize_csr(r->context, "get-tgt", r->req, p);
+
+ /*
+ * We tolerate EACCES because we support partial approval.
+ *
+ * (KRB5_PLUGIN_NO_HANDLE means no plugin handled the authorization
+ * check.)
+ */
+ if (ret == EACCES || ret == KRB5_PLUGIN_NO_HANDLE)
+ ret = 0;
+ if (ret) {
+ krb5_free_principal(r->context, p);
+ return bad_403(r, ret, "Permission denied");
+ }
+ }
+ }
+ if (ret == 0) {
+ /*
+ * Get the actual TGTs that were authorized.
+ *
+ * get_tgts_param_execute_cb() calls bad_req()
+ */
+ r->error_code = 0;
+ res = MHD_get_connection_values(r->connection, MHD_GET_ARGUMENT_KIND,
+ get_tgts_param_execute_cb, r);
+ if (r->response || res == MHD_NO) {
+ krb5_free_principal(r->context, p);
+ return res;
+ }
+ ret = r->error_code;
+ }
+ krb5_free_principal(r->context, p);
+ hx509_request_free(&r->req);
+ r->req = NULL;
+
+ /*
+ * get_tgts_param_execute_cb() will write its JSON response to the file
+ * named by r->ccname.
+ */
+ if (fflush(r->tgts) != 0)
+ return bad_503(r, ret, "Could not get TGT");
+ if ((errno = rk_undumpdata(r->tgts_filename, &body, &bodylen)))
+ return bad_503(r, ret, "Could not get TGT");
+
+ ret = resp(r, MHD_HTTP_OK, MHD_RESPMEM_MUST_COPY,
+ "application/x-krb5-ccaches-json", body, bodylen, NULL);
+ free(body);
+ return ret;
+}
+
+static krb5_error_code
+health(const char *method, struct bx509_request_desc *r)
+{
+ if (strcmp(method, "HEAD") == 0)
+ return resp(r, MHD_HTTP_OK, MHD_RESPMEM_PERSISTENT, NULL, "", 0, NULL);
+ return resp(r, MHD_HTTP_OK, MHD_RESPMEM_PERSISTENT, NULL,
+ "To determine the health of the service, use the /bx509 "
+ "end-point.\n",
+ sizeof("To determine the health of the service, use the "
+ "/bx509 end-point.\n") - 1, NULL);
+
+}
+
+static krb5_error_code
+mac_csrf_token(struct bx509_request_desc *r, krb5_storage *sp)
+{
+ krb5_error_code ret;
+ krb5_data data;
+ char mac[EVP_MAX_MD_SIZE];
+ unsigned int maclen = sizeof(mac);
+ HMAC_CTX *ctx = NULL;
+
+ ret = krb5_storage_to_data(sp, &data);
+ if (ret == 0 && (ctx = HMAC_CTX_new()) == NULL)
+ ret = krb5_enomem(r->context);
+ /* HMAC the token body and the client principal name */
+ if (ret == 0) {
+ if (HMAC_Init_ex(ctx, csrf_key, sizeof(csrf_key),
+ EVP_sha256(),
+ NULL) == 0) {
+ HMAC_CTX_cleanup(ctx);
+ ret = krb5_enomem(r->context);
+ } else {
+ HMAC_Update(ctx, data.data, data.length);
+ if (r->cname)
+ HMAC_Update(ctx, r->cname, strlen(r->cname));
+ HMAC_Final(ctx, mac, &maclen);
+ HMAC_CTX_cleanup(ctx);
+ krb5_data_free(&data);
+ data.length = maclen;
+ data.data = mac;
+ if (krb5_storage_write(sp, mac, maclen) != maclen)
+ ret = krb5_enomem(r->context);
+ }
+ }
+ if (ctx)
+ HMAC_CTX_free(ctx);
+ return ret;
+}
+
+/*
+ * Make a CSRF token. If one is also given, make one with the same body
+ * content so we can check the HMAC.
+ *
+ * Outputs the token and its age. Do not use either if the token does not
+ * equal the given token.
+ */
+static krb5_error_code
+make_csrf_token(struct bx509_request_desc *r,
+ const char *given,
+ char **token,
+ int64_t *age)
+{
+ krb5_error_code ret = 0;
+ unsigned char given_decoded[128];
+ krb5_storage *sp = NULL;
+ krb5_data data;
+ ssize_t dlen = -1;
+ uint64_t nonce;
+ int64_t t = 0;
+
+
+ *age = 0;
+ data.data = NULL;
+ data.length = 0;
+ if (given) {
+ size_t len = strlen(given);
+
+ /* Extract issue time and nonce from token */
+ if (len >= sizeof(given_decoded))
+ ret = ERANGE;
+ if (ret == 0 && (dlen = rk_base64_decode(given, &given_decoded)) <= 0)
+ ret = errno;
+ if (ret == 0 &&
+ (sp = krb5_storage_from_mem(given_decoded, dlen)) == NULL)
+ ret = krb5_enomem(r->context);
+ if (ret == 0)
+ ret = krb5_ret_int64(sp, &t);
+ if (ret == 0)
+ ret = krb5_ret_uint64(sp, &nonce);
+ krb5_storage_free(sp);
+ sp = NULL;
+ if (ret == 0)
+ *age = time(NULL) - t;
+ } else {
+ t = time(NULL);
+ krb5_generate_random_block((void *)&nonce, sizeof(nonce));
+ }
+
+ if (ret == 0 && (sp = krb5_storage_emem()) == NULL)
+ ret = krb5_enomem(r->context);
+ if (ret == 0)
+ ret = krb5_store_int64(sp, t);
+ if (ret == 0)
+ ret = krb5_store_uint64(sp, nonce);
+ if (ret == 0)
+ ret = mac_csrf_token(r, sp);
+ if (ret == 0)
+ ret = krb5_storage_to_data(sp, &data);
+ if (ret == 0 && data.length > INT_MAX)
+ ret = ERANGE;
+ if (ret == 0 &&
+ rk_base64_encode(data.data, data.length, token) < 0)
+ ret = errno;
+ krb5_storage_free(sp);
+ krb5_data_free(&data);
+ return ret;
+}
+
+static heim_mhd_result
+validate_csrf_token(struct bx509_request_desc *r)
+{
+ const char *given;
+ int64_t age;
+ krb5_error_code ret;
+
+ if ((((csrf_prot_type & CSRF_PROT_GET_WITH_HEADER) &&
+ strcmp(r->method, "GET") == 0) ||
+ ((csrf_prot_type & CSRF_PROT_POST_WITH_HEADER) &&
+ strcmp(r->method, "POST") == 0)) &&
+ MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
+ csrf_header) == NULL) {
+ ret = bad_req(r, EACCES, MHD_HTTP_FORBIDDEN,
+ "Request must have header \"%s\"", csrf_header);
+ return ret == -1 ? MHD_NO : MHD_YES;
+ }
+
+ if (strcmp(r->method, "GET") == 0 &&
+ !(csrf_prot_type & CSRF_PROT_GET_WITH_TOKEN))
+ return 0;
+ if (strcmp(r->method, "POST") == 0 &&
+ !(csrf_prot_type & CSRF_PROT_POST_WITH_TOKEN))
+ return 0;
+
+ given = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
+ "X-CSRF-Token");
+ ret = make_csrf_token(r, given, &r->csrf_token, &age);
+ if (ret)
+ return bad_503(r, ret, "Could not make or validate CSRF token");
+ if (given == NULL)
+ return bad_req(r, EACCES, MHD_HTTP_FORBIDDEN,
+ "CSRF token needed; copy the X-CSRF-Token: response "
+ "header to your next POST");
+ if (strlen(given) != strlen(r->csrf_token) ||
+ strcmp(given, r->csrf_token) != 0)
+ return bad_403(r, EACCES, "Invalid CSRF token");
+ if (age > 300)
+ return bad_403(r, EACCES, "CSRF token expired");
+ return 0;
+}
+
+/*
+ * MHD callback to free the request context when MHD is done sending the
+ * response.
+ */
+static void
+cleanup_req(void *cls,
+ struct MHD_Connection *connection,
+ void **con_cls,
+ enum MHD_RequestTerminationCode toe)
+{
+ struct bx509_request_desc *r = *con_cls;
+
+ (void)cls;
+ (void)connection;
+ (void)toe;
+ clean_req_desc(r);
+ *con_cls = NULL;
+}
+
+/* Callback for MHD POST form data processing */
+static heim_mhd_result
+ip(void *cls,
+ enum MHD_ValueKind kind,
+ const char *key,
+ const char *content_name,
+ const char *content_type,
+ const char *transfer_encoding,
+ const char *val,
+ uint64_t off,
+ size_t size)
+{
+ struct bx509_request_desc *r = cls;
+ struct free_tend_list *ftl = calloc(1, sizeof(*ftl));
+ char *keydup = strdup(key);
+ char *valdup = strndup(val, size);
+
+ (void)content_name; /* MIME attachment name */
+ (void)content_type; /* Don't care -- MHD liked it */
+ (void)transfer_encoding;
+ (void)off; /* Offset in POST data */
+
+ /*
+ * We're going to MHD_set_connection_value(), but we need copies because
+ * the MHD POST processor quite naturally keeps none of the chunks
+ * received.
+ */
+ if (ftl == NULL || keydup == NULL || valdup == NULL) {
+ free(ftl);
+ free(keydup);
+ free(valdup);
+ return MHD_NO;
+ }
+ ftl->freeme1 = keydup;
+ ftl->freeme2 = valdup;
+ ftl->next = r->free_list;
+ r->free_list = ftl;
+
+ return MHD_set_connection_value(r->connection, MHD_GET_ARGUMENT_KIND,
+ keydup, valdup);
+}
+
+typedef krb5_error_code (*handler)(struct bx509_request_desc *);
+
+struct route {
+ const char *local_part;
+ handler h;
+ unsigned int referer_ok:1;
+} routes[] = {
+ { "/get-cert", bx509, 0 },
+ { "/get-negotiate-token", bnegotiate, 1 },
+ { "/get-tgt", get_tgt, 0 },
+ { "/get-tgts", get_tgts, 0 },
+ /* Lousy old names to be removed eventually */
+ { "/bnegotiate", bnegotiate, 1 },
+ { "/bx509", bx509, 0 },
+};
+
+/*
+ * We should commonalize all of:
+ *
+ * - route() and related infrastructure
+ * - including the CSRF functions
+ * - and Negotiate/Bearer authentication
+ *
+ * so that we end up with a simple framework that our daemons can invoke to
+ * serve simple functions that take a fully-consumed request and send a
+ * response.
+ *
+ * Then:
+ *
+ * - split out the CA and non-CA bits into separate daemons using that common
+ * code,
+ * - make httpkadmind use that common code,
+ * - abstract out all the MHD stuff.
+ */
+
+/* Routes requests */
+static heim_mhd_result
+route(void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **ctx)
+{
+ struct bx509_request_desc *r = *ctx;
+ size_t i;
+ int ret;
+
+ if (r == NULL) {
+ /*
+ * This is the first call, right after headers were read.
+ *
+ * We must return quickly so that any 100-Continue might be sent with
+ * celerity. We want to make sure to send any 401s early, so we check
+ * WWW-Authenticate now, not later.
+ *
+ * We'll get called again to really do the processing. If we're
+ * handling a POST then we'll also get called with upload_data != NULL,
+ * possibly multiple times.
+ */
+ if ((ret = set_req_desc(connection, method, url, &r)))
+ return MHD_NO;
+ *ctx = r;
+
+ /* All requests other than /health require authentication */
+ if (strcmp(url, "/health") == 0)
+ return MHD_YES;
+
+ /*
+ * Authenticate and do CSRF protection.
+ *
+ * If the Referer: header is set in the request, we don't want CSRF
+ * protection as only /get-negotiate-token will accept a Referer:
+ * header (see routes[] and below), so we'll call validate_csrf_token()
+ * for the other routes or reject the request for having Referer: set.
+ */
+ ret = validate_token(r);
+ if (ret == 0 &&
+ MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND, "Referer") == NULL)
+ ret = validate_csrf_token(r);
+
+ /*
+ * As this is the initial call to this handler, we must return now.
+ *
+ * If authentication or CSRF protection failed then we'll already have
+ * enqueued a 401, 403, or 5xx response and then we're done.
+ *
+ * If both authentication and CSRF protection succeeded then no
+ * response has been queued up and we'll get called again to finally
+ * process the request, then this entire if block will not be executed.
+ */
+ return ret == -1 ? MHD_NO : MHD_YES;
+ }
+
+ /* Validate HTTP method */
+ if (strcmp(method, "GET") != 0 &&
+ strcmp(method, "POST") != 0 &&
+ strcmp(method, "HEAD") != 0) {
+ return bad_405(r, method) == -1 ? MHD_NO : MHD_YES;
+ }
+
+ if ((strcmp(method, "HEAD") == 0 || strcmp(method, "GET") == 0) &&
+ (strcmp(url, "/health") == 0 || strcmp(url, "/") == 0)) {
+ /* /health end-point -- no authentication, no CSRF, no nothing */
+ return health(method, r) == -1 ? MHD_NO : MHD_YES;
+ }
+
+ if (r->cname == NULL)
+ return bad_401(r, "Authorization token is missing");
+
+ if (strcmp(method, "POST") == 0 && *upload_data_size != 0) {
+ /*
+ * Consume all the POST body and set form data as MHD_GET_ARGUMENT_KIND
+ * (as if they had been URI query parameters).
+ *
+ * We have to do this before we can MHD_queue_response() as MHD will
+ * not consume the rest of the request body on its own, so it's an
+ * error to MHD_queue_response() before we've done this, and if we do
+ * then MHD just closes the connection.
+ *
+ * 4KB should be more than enough buffer space for all the keys we
+ * expect.
+ */
+ if (r->pp == NULL)
+ r->pp = MHD_create_post_processor(connection, 4096, ip, r);
+ if (r->pp == NULL) {
+ ret = bad_503(r, errno ? errno : ENOMEM,
+ "Could not consume POST data");
+ return ret == -1 ? MHD_NO : MHD_YES;
+ }
+ if (r->post_data_size + *upload_data_size > 1UL<<17) {
+ return bad_413(r) == -1 ? MHD_NO : MHD_YES;
+ }
+ r->post_data_size += *upload_data_size;
+ if (MHD_post_process(r->pp, upload_data,
+ *upload_data_size) == MHD_NO) {
+ ret = bad_503(r, errno ? errno : ENOMEM,
+ "Could not consume POST data");
+ return ret == -1 ? MHD_NO : MHD_YES;
+ }
+ *upload_data_size = 0;
+ return MHD_YES;
+ }
+
+ /*
+ * Either this is a HEAD, a GET, or a POST whose request body has now been
+ * received completely and processed.
+ */
+
+ /* Allow GET? */
+ if (strcmp(method, "GET") == 0 && !allow_GET_flag) {
+ /* No */
+ return bad_405(r, method) == -1 ? MHD_NO : MHD_YES;
+ }
+
+ for (i = 0; i < sizeof(routes)/sizeof(routes[0]); i++) {
+ if (strcmp(url, routes[i].local_part) != 0)
+ continue;
+ if (!routes[i].referer_ok &&
+ MHD_lookup_connection_value(r->connection,
+ MHD_HEADER_KIND,
+ "Referer") != NULL) {
+ ret = bad_req(r, EACCES, MHD_HTTP_FORBIDDEN,
+ "GET from browser not allowed");
+ return ret == -1 ? MHD_NO : MHD_YES;
+ }
+ if (strcmp(method, "HEAD") == 0)
+ ret = resp(r, MHD_HTTP_OK, MHD_RESPMEM_PERSISTENT, NULL, "", 0,
+ NULL);
+ else
+ ret = routes[i].h(r);
+ return ret == -1 ? MHD_NO : MHD_YES;
+ }
+
+ ret = bad_404(r, url);
+ return ret == -1 ? MHD_NO : MHD_YES;
+}
+
+static struct getargs args[] = {
+ { "help", 'h', arg_flag, &help_flag, "Print usage message", NULL },
+ { "version", '\0', arg_flag, &version_flag, "Print version", NULL },
+ { NULL, 'H', arg_strings, &audiences,
+ "expected token audience(s)", "HOSTNAME" },
+ { "daemon", 'd', arg_flag, &daemonize, "daemonize", "daemonize" },
+ { "daemon-child", 0, arg_flag, &daemon_child_fd, NULL, NULL }, /* priv */
+ { "reverse-proxied", 0, arg_flag, &reverse_proxied_flag,
+ "reverse proxied", "listen on 127.0.0.1 and do not use TLS" },
+ { "port", 'p', arg_integer, &port, "port number (default: 443)", "PORT" },
+ { "cache-dir", 0, arg_string, &cache_dir,
+ "cache directory", "DIRECTORY" },
+ { "allow-GET", 0, arg_negative_flag, &allow_GET_flag, NULL, NULL },
+ { "csrf-header", 0, arg_flag,
+ &csrf_header, "required request header", "HEADER-NAME" },
+ { "csrf-protection-type", 0, arg_strings, &csrf_prot_type_strs,
+ "Anti-CSRF protection type", "TYPE" },
+ { "csrf-key-file", 0, arg_string, &csrf_key_file,
+ "CSRF MAC key", "FILE" },
+ { "cert", 0, arg_string, &cert_file,
+ "certificate file path (PEM)", "HX509-STORE" },
+ { "private-key", 0, arg_string, &priv_key_file,
+ "private key file path (PEM)", "HX509-STORE" },
+ { "thread-per-client", 't', arg_flag, &thread_per_client_flag,
+ "thread per-client", "use thread per-client" },
+ { "verbose", 'v', arg_counter, &verbose_counter, "verbose", "run verbosely" }
+};
+
+static int
+usage(int e)
+{
+ arg_printusage(args, sizeof(args) / sizeof(args[0]), "bx509",
+ "\nServes RESTful GETs of /get-cert, /get-tgt, /get-tgts, and\n"
+ "/get-negotiate-toke, performing corresponding kx509 and, \n"
+ "possibly, PKINIT requests to the KDCs of the requested \n"
+ "realms (or just the given REALM).\n");
+ exit(e);
+}
+
+static int sigpipe[2] = { -1, -1 };
+
+static void
+sighandler(int sig)
+{
+ char c = sig;
+ while (write(sigpipe[1], &c, sizeof(c)) == -1 && errno == EINTR)
+ ;
+}
+
+static void
+bx509_openlog(krb5_context context,
+ const char *svc,
+ krb5_log_facility **fac)
+{
+ char **s = NULL, **p;
+
+ krb5_initlog(context, "bx509d", fac);
+ s = krb5_config_get_strings(context, NULL, svc, "logging", NULL);
+ if (s == NULL)
+ s = krb5_config_get_strings(context, NULL, "logging", svc, NULL);
+ if (s) {
+ for(p = s; *p; p++)
+ krb5_addlog_dest(context, *fac, *p);
+ krb5_config_free_strings(s);
+ } else {
+ char *ss;
+ if (asprintf(&ss, "0-1/FILE:%s/%s", hdb_db_dir(context),
+ KDC_LOG_FILE) < 0)
+ err(1, "out of memory");
+ krb5_addlog_dest(context, *fac, ss);
+ free(ss);
+ }
+ krb5_set_warn_dest(context, *fac);
+}
+
+static const char *sysplugin_dirs[] = {
+#ifdef _WIN32
+ "$ORIGIN",
+#else
+ "$ORIGIN/../lib/plugin/kdc",
+#endif
+#ifdef __APPLE__
+ LIBDIR "/plugin/kdc",
+#endif
+ NULL
+};
+
+static void
+load_plugins(krb5_context context)
+{
+ const char * const *dirs = sysplugin_dirs;
+#ifndef _WIN32
+ char **cfdirs;
+
+ cfdirs = krb5_config_get_strings(context, NULL, "kdc", "plugin_dir", NULL);
+ if (cfdirs)
+ dirs = (const char * const *)cfdirs;
+#endif
+
+ /* XXX kdc? */
+ _krb5_load_plugins(context, "kdc", (const char **)dirs);
+
+#ifndef _WIN32
+ krb5_config_free_strings(cfdirs);
+#endif
+}
+
+static void
+get_csrf_prot_type(krb5_context context)
+{
+ char * const *strs = csrf_prot_type_strs.strings;
+ size_t n = csrf_prot_type_strs.num_strings;
+ size_t i;
+ char **freeme = NULL;
+
+ if (csrf_header == NULL)
+ csrf_header = krb5_config_get_string(context, NULL, "bx509d",
+ "csrf_protection_csrf_header",
+ NULL);
+
+ if (n == 0) {
+ char * const *p;
+
+ strs = freeme = krb5_config_get_strings(context, NULL, "bx509d",
+ "csrf_protection_type", NULL);
+ for (p = strs; p && p; p++)
+ n++;
+ }
+
+ for (i = 0; i < n; i++) {
+ if (strcmp(strs[i], "GET-with-header") == 0)
+ csrf_prot_type |= CSRF_PROT_GET_WITH_HEADER;
+ else if (strcmp(strs[i], "GET-with-token") == 0)
+ csrf_prot_type |= CSRF_PROT_GET_WITH_TOKEN;
+ else if (strcmp(strs[i], "POST-with-header") == 0)
+ csrf_prot_type |= CSRF_PROT_POST_WITH_HEADER;
+ else if (strcmp(strs[i], "POST-with-token") == 0)
+ csrf_prot_type |= CSRF_PROT_POST_WITH_TOKEN;
+ }
+ free(freeme);
+
+ /*
+ * For GETs we default to no CSRF protection as our GETable resources are
+ * safe and idempotent and we count on the browser not to make the
+ * responses available to cross-site requests.
+ *
+ * But, really, we don't want browsers even making these requests since, if
+ * the browsers behave correctly, then there's no point, and if they don't
+ * behave correctly then that could be catastrophic. Of course, there's no
+ * guarantee that a browser won't have other catastrophic bugs, but still,
+ * we should probably change this default in the future:
+ *
+ * if (!(csrf_prot_type & CSRF_PROT_GET_WITH_HEADER) &&
+ * !(csrf_prot_type & CSRF_PROT_GET_WITH_TOKEN))
+ * csrf_prot_type |= <whatever-the-new-default-should-be>;
+ */
+
+ /*
+ * For POSTs we default to CSRF protection with anti-CSRF tokens even
+ * though out POSTable resources are safe and idempotent when POSTed and we
+ * could count on the browser not to make the responses available to
+ * cross-site requests.
+ */
+ if (!(csrf_prot_type & CSRF_PROT_POST_WITH_HEADER) &&
+ !(csrf_prot_type & CSRF_PROT_POST_WITH_TOKEN))
+ csrf_prot_type |= CSRF_PROT_POST_WITH_TOKEN;
+}
+
+int
+main(int argc, char **argv)
+{
+ unsigned int flags = MHD_USE_THREAD_PER_CONNECTION; /* XXX */
+ struct sockaddr_in sin;
+ struct MHD_Daemon *previous = NULL;
+ struct MHD_Daemon *current = NULL;
+ struct sigaction sa;
+ krb5_context context = NULL;
+ MHD_socket sock = MHD_INVALID_SOCKET;
+ char *priv_key_pem = NULL;
+ char *cert_pem = NULL;
+ char sig;
+ int optidx = 0;
+ int ret;
+
+ setprogname("bx509d");
+ if (getarg(args, sizeof(args) / sizeof(args[0]), argc, argv, &optidx))
+ usage(1);
+ if (help_flag)
+ usage(0);
+ if (version_flag) {
+ print_version(NULL);
+ exit(0);
+ }
+ if (argc > optidx) /* Add option to set a URI local part prefix? */
+ usage(1);
+ if (port < 0)
+ errx(1, "Port number must be given");
+
+ if ((errno = pthread_key_create(&k5ctx, k5_free_context)))
+ err(1, "Could not create thread-specific storage");
+
+ if ((errno = get_krb5_context(&context)))
+ err(1, "Could not init krb5 context");
+
+ bx509_openlog(context, "bx509d", &logfac);
+ krb5_set_log_dest(context, logfac);
+ load_plugins(context);
+
+ if (allow_GET_flag == -1)
+ warnx("It is safer to use --no-allow-GET");
+
+ get_csrf_prot_type(context);
+
+ krb5_generate_random_block((void *)&csrf_key, sizeof(csrf_key));
+ if (csrf_key_file == NULL)
+ csrf_key_file = krb5_config_get_string(context, NULL, "bx509d",
+ "csrf_key_file", NULL);
+ if (csrf_key_file) {
+ ssize_t bytes;
+ int fd;
+
+ fd = open(csrf_key_file, O_RDONLY);
+ if (fd == -1)
+ err(1, "CSRF key file missing %s", csrf_key_file);
+ bytes = read(fd, csrf_key, sizeof(csrf_key));
+ if (bytes == -1)
+ err(1, "Could not read CSRF key file %s", csrf_key_file);
+ if (bytes != sizeof(csrf_key))
+ errx(1, "CSRF key file too small (should be %lu) %s",
+ (unsigned long)sizeof(csrf_key), csrf_key_file);
+ }
+
+ if (audiences.num_strings == 0) {
+ char localhost[MAXHOSTNAMELEN];
+
+ ret = gethostname(localhost, sizeof(localhost));
+ if (ret == -1)
+ errx(1, "Could not determine local hostname; use --audience");
+
+ if ((audiences.strings =
+ calloc(1, sizeof(audiences.strings[0]))) == NULL ||
+ (audiences.strings[0] = strdup(localhost)) == NULL)
+ err(1, "Out of memory");
+ audiences.num_strings = 1;
+ }
+
+ if (daemonize && daemon_child_fd == -1)
+ daemon_child_fd = roken_detach_prep(argc, argv, "--daemon-child");
+ daemonize = 0;
+
+ argc -= optidx;
+ argv += optidx;
+ if (argc != 0)
+ usage(1);
+
+ if (cache_dir == NULL) {
+ char *s = NULL;
+
+ if (asprintf(&s, "%s/bx509d-XXXXXX",
+ getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp") == -1 ||
+ s == NULL ||
+ (cache_dir = mkdtemp(s)) == NULL)
+ err(1, "could not create temporary cache directory");
+ if (verbose_counter)
+ fprintf(stderr, "Note: using %s as cache directory\n", cache_dir);
+ atexit(rm_cache_dir);
+ setenv("TMPDIR", cache_dir, 1);
+ }
+
+ generate_key(context->hx509ctx, "impersonation", "rsa", 2048, &impersonation_key_fn);
+
+again:
+ if (cert_file && !priv_key_file)
+ priv_key_file = cert_file;
+
+ if (cert_file) {
+ hx509_cursor cursor = NULL;
+ hx509_certs certs = NULL;
+ hx509_cert cert = NULL;
+ time_t min_cert_life = 0;
+ size_t len;
+ void *s;
+
+ ret = hx509_certs_init(context->hx509ctx, cert_file, 0, NULL, &certs);
+ if (ret == 0)
+ ret = hx509_certs_start_seq(context->hx509ctx, certs, &cursor);
+ while (ret == 0 &&
+ (ret = hx509_certs_next_cert(context->hx509ctx, certs,
+ cursor, &cert)) == 0 && cert) {
+ time_t notAfter = 0;
+
+ if (!hx509_cert_have_private_key_only(cert) &&
+ (notAfter = hx509_cert_get_notAfter(cert)) <= time(NULL) + 30)
+ errx(1, "One or more certificates in %s are expired",
+ cert_file);
+ if (notAfter) {
+ notAfter -= time(NULL);
+ if (notAfter < 600)
+ warnx("One or more certificates in %s expire soon",
+ cert_file);
+ /* Reload 5 minutes prior to expiration */
+ if (notAfter < min_cert_life || min_cert_life < 1)
+ min_cert_life = notAfter;
+ }
+ hx509_cert_free(cert);
+ }
+ if (certs)
+ (void) hx509_certs_end_seq(context->hx509ctx, certs, cursor);
+ if (min_cert_life > 4)
+ alarm(min_cert_life >> 1);
+ hx509_certs_free(&certs);
+ if (ret)
+ hx509_err(context->hx509ctx, 1, ret,
+ "could not read certificate from %s", cert_file);
+
+ if ((errno = rk_undumpdata(cert_file, &s, &len)) ||
+ (cert_pem = strndup(s, len)) == NULL)
+ err(1, "could not read certificate from %s", cert_file);
+ if (strlen(cert_pem) != len)
+ err(1, "NULs in certificate file contents: %s", cert_file);
+ free(s);
+ }
+
+ if (priv_key_file) {
+ size_t len;
+ void *s;
+
+ if ((errno = rk_undumpdata(priv_key_file, &s, &len)) ||
+ (priv_key_pem = strndup(s, len)) == NULL)
+ err(1, "could not read private key from %s", priv_key_file);
+ if (strlen(priv_key_pem) != len)
+ err(1, "NULs in private key file contents: %s", priv_key_file);
+ free(s);
+ }
+
+ if (verbose_counter > 1)
+ flags |= MHD_USE_DEBUG;
+ if (thread_per_client_flag)
+ flags |= MHD_USE_THREAD_PER_CONNECTION;
+
+
+ if (pipe(sigpipe) == -1)
+ err(1, "Could not set up key/cert reloading");
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = sighandler;
+ if (reverse_proxied_flag) {
+ /*
+ * We won't use TLS in the reverse proxy case, so no need to reload
+ * certs. But we'll still read them if given, and alarm() will get
+ * called.
+ */
+ (void) signal(SIGHUP, SIG_IGN);
+ (void) signal(SIGUSR1, SIG_IGN);
+ (void) signal(SIGALRM, SIG_IGN);
+ } else {
+ (void) sigaction(SIGHUP, &sa, NULL); /* Reload key & cert */
+ (void) sigaction(SIGUSR1, &sa, NULL); /* Reload key & cert */
+ (void) sigaction(SIGALRM, &sa, NULL); /* Reload key & cert */
+ }
+ (void) sigaction(SIGINT, &sa, NULL); /* Graceful shutdown */
+ (void) sigaction(SIGTERM, &sa, NULL); /* Graceful shutdown */
+ (void) signal(SIGPIPE, SIG_IGN);
+
+ if (previous)
+ sock = MHD_quiesce_daemon(previous);
+
+ if (reverse_proxied_flag) {
+ /*
+ * XXX IPv6 too. Create the sockets and tell MHD_start_daemon() about
+ * them.
+ */
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(port);
+ current = MHD_start_daemon(flags, port,
+ /*
+ * This is a connection access callback. We
+ * don't use it.
+ */
+ NULL, NULL,
+ /* This is our request handler */
+ route, (char *)NULL,
+ MHD_OPTION_SOCK_ADDR, &sin,
+ MHD_OPTION_CONNECTION_LIMIT, (unsigned int)200,
+ MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)10,
+ /* This is our request cleanup handler */
+ MHD_OPTION_NOTIFY_COMPLETED, cleanup_req, NULL,
+ MHD_OPTION_END);
+ } else if (sock != MHD_INVALID_SOCKET) {
+ /*
+ * Restart following a possible certificate/key rollover, reusing the
+ * listen socket returned by MHD_quiesce_daemon().
+ */
+ current = MHD_start_daemon(flags | MHD_USE_SSL, port,
+ NULL, NULL,
+ route, (char *)NULL,
+ MHD_OPTION_HTTPS_MEM_KEY, priv_key_pem,
+ MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
+ MHD_OPTION_CONNECTION_LIMIT, (unsigned int)200,
+ MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)10,
+ MHD_OPTION_NOTIFY_COMPLETED, cleanup_req, NULL,
+ MHD_OPTION_LISTEN_SOCKET, sock,
+ MHD_OPTION_END);
+ sock = MHD_INVALID_SOCKET;
+ } else {
+ /*
+ * Initial MHD_start_daemon(), with TLS.
+ *
+ * Subsequently we'll restart reusing the listen socket this creates.
+ * See above.
+ */
+ current = MHD_start_daemon(flags | MHD_USE_SSL, port,
+ NULL, NULL,
+ route, (char *)NULL,
+ MHD_OPTION_HTTPS_MEM_KEY, priv_key_pem,
+ MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
+ MHD_OPTION_CONNECTION_LIMIT, (unsigned int)200,
+ MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)10,
+ MHD_OPTION_NOTIFY_COMPLETED, cleanup_req, NULL,
+ MHD_OPTION_END);
+ }
+ if (current == NULL)
+ err(1, "Could not start bx509 REST service");
+
+ if (previous) {
+ MHD_stop_daemon(previous);
+ previous = NULL;
+ }
+
+ if (verbose_counter)
+ fprintf(stderr, "Ready!\n");
+ if (daemon_child_fd != -1)
+ roken_detach_finish(NULL, daemon_child_fd);
+
+ /* Wait for signal, possibly SIGALRM, to reload certs and/or exit */
+ while ((ret = read(sigpipe[0], &sig, sizeof(sig))) == -1 &&
+ errno == EINTR)
+ ;
+
+ free(priv_key_pem);
+ free(cert_pem);
+ priv_key_pem = NULL;
+ cert_pem = NULL;
+
+ if (ret == 1 && (sig == SIGHUP || sig == SIGUSR1 || sig == SIGALRM)) {
+ /* Reload certs and restart service gracefully */
+ previous = current;
+ current = NULL;
+ goto again;
+ }
+
+ MHD_stop_daemon(current);
+ _krb5_unload_plugins(context, "kdc");
+ pthread_key_delete(k5ctx);
+ return 0;
+}
diff --git a/third_party/heimdal/kdc/ca.c b/third_party/heimdal/kdc/ca.c
new file mode 100644
index 0000000..4402c44
--- /dev/null
+++ b/third_party/heimdal/kdc/ca.c
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2019 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "kdc_locl.h"
+#include <hex.h>
+#include <rfc2459_asn1.h>
+#include "../lib/hx509/hx_locl.h"
+
+#include <stdarg.h>
+
+/*
+ * This file implements a singular utility function `kdc_issue_certificate()'
+ * for certificate issuance for kx509 and bx509, which takes a principal name,
+ * an `hx509_request' resulting from parsing a CSR and possibly adding
+ * SAN/EKU/KU extensions, the start/end times of request's authentication
+ * method, and whether to include a full certificate chain in the result.
+ */
+
+/*
+ * Get a configuration sub-tree for kx509 based on what's being requested and
+ * by whom.
+ *
+ * We have a number of cases:
+ *
+ * - default certificate (no CSR used, or no certificate extensions requested)
+ * - for client principals
+ * - for service principals
+ * - client certificate requested (CSR used and client-y SANs/EKUs requested)
+ * - server certificate requested (CSR used and server-y SANs/EKUs requested)
+ * - mixed client/server certificate requested (...)
+ */
+static krb5_error_code
+get_cf(krb5_context context,
+ const char *app_name,
+ krb5_log_facility *logf,
+ hx509_request req,
+ krb5_principal cprinc,
+ const krb5_config_binding **cf)
+{
+ krb5_error_code ret = ENOTSUP;
+ const char *realm = krb5_principal_get_realm(context, cprinc);
+
+ *cf = NULL;
+ if (strcmp(app_name, "kdc") == 0)
+ *cf = krb5_config_get_list(context, NULL, app_name, "realms", realm,
+ "kx509", NULL);
+ else
+ *cf = krb5_config_get_list(context, NULL, app_name, "realms", realm,
+ NULL);
+ if (*cf)
+ ret = 0;
+ if (ret) {
+ krb5_log_msg(context, logf, 3, NULL,
+ "No %s configuration for certification authority [%s] "
+ "realm %s -> kx509 -> ...", app_name,
+ strcmp(app_name, "bx509") == 0 ? "bx509" : "kx509",
+ realm);
+ krb5_set_error_message(context, KRB5KDC_ERR_POLICY,
+ "No %s configuration for certification authority [%s] "
+ "realm %s -> kx509 -> ...", app_name,
+ strcmp(app_name, "bx509") == 0 ? "bx509" : "kx509",
+ realm);
+ }
+ return ret;
+}
+
+/*
+ * Build a certifate for `principal' and its CSR.
+ */
+KDC_LIB_FUNCTION krb5_error_code KDC_LIB_CALL
+kdc_issue_certificate(krb5_context context,
+ const char *app_name,
+ krb5_log_facility *logf,
+ hx509_request req,
+ krb5_principal cprinc,
+ krb5_times *auth_times,
+ time_t req_life,
+ int send_chain,
+ hx509_certs *out)
+{
+ const krb5_config_binding *cf;
+ krb5_error_code ret = KRB5KDC_ERR_POLICY;
+ KRB5PrincipalName cprinc2;
+
+ *out = NULL;
+ cprinc2.principalName = cprinc->name;
+ cprinc2.realm = cprinc->realm;
+
+ /* Get configuration */
+ ret = get_cf(context, app_name, logf, req, cprinc, &cf);
+ if (ret == 0)
+ ret = _hx509_ca_issue_certificate(context->hx509ctx,
+ (const heim_config_binding *)cf,
+ logf, req, &cprinc2,
+ auth_times->starttime,
+ auth_times->endtime,
+ req_life,
+ send_chain,
+ out);
+ if (ret == EACCES)
+ ret = KRB5KDC_ERR_POLICY;
+ return ret;
+}
diff --git a/third_party/heimdal/kdc/cjwt_token_validator.c b/third_party/heimdal/kdc/cjwt_token_validator.c
new file mode 100644
index 0000000..93742e5
--- /dev/null
+++ b/third_party/heimdal/kdc/cjwt_token_validator.c
@@ -0,0 +1,343 @@
+/*
+ * Copyright (c) 2019 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This is a plugin by which bx509d can validate JWT Bearer tokens using the
+ * cjwt library.
+ *
+ * Configuration:
+ *
+ * [kdc]
+ * realm = {
+ * A.REALM.NAME = {
+ * cjwt_jqk = PATH-TO-JWK-PEM-FILE
+ * }
+ * }
+ *
+ * where AUDIENCE-FOR-KDC is the value of the "audience" (i.e., the target) of
+ * the token.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <string.h>
+#include <heimbase.h>
+#include <krb5.h>
+#include <common_plugin.h>
+#include <hdb.h>
+#include <roken.h>
+#include <token_validator_plugin.h>
+#include <cjwt/cjwt.h>
+#ifdef HAVE_CJSON
+#include <cJSON.h>
+#endif
+
+static const char *
+get_kv(krb5_context context, const char *realm, const char *k, const char *k2)
+{
+ return krb5_config_get_string(context, NULL, "bx509", "realms", realm,
+ k, k2, NULL);
+}
+
+static krb5_error_code
+get_issuer_pubkeys(krb5_context context,
+ const char *realm,
+ krb5_data *previous,
+ krb5_data *current,
+ krb5_data *next)
+{
+ krb5_error_code save_ret = 0;
+ krb5_error_code ret;
+ const char *v;
+ size_t nkeys = 0;
+
+ previous->data = current->data = next->data = 0;
+ previous->length = current->length = next->length = 0;
+
+ if ((v = get_kv(context, realm, "cjwt_jwk_next", NULL)) &&
+ (++nkeys) &&
+ (ret = rk_undumpdata(v, &next->data, &next->length)))
+ save_ret = ret;
+ if ((v = get_kv(context, realm, "cjwt_jwk_previous", NULL)) &&
+ (++nkeys) &&
+ (ret = rk_undumpdata(v, &previous->data, &previous->length)) &&
+ save_ret == 0)
+ save_ret = ret;
+ if ((v = get_kv(context, realm, "cjwt_jwk_current", NULL)) &&
+ (++nkeys) &&
+ (ret = rk_undumpdata(v, &current->data, &current->length)) &&
+ save_ret == 0)
+ save_ret = ret;
+ if (nkeys == 0)
+ krb5_set_error_message(context, EINVAL, "jwk issuer key not specified in "
+ "[bx509]->realm->%s->cjwt_jwk_{previous,current,next}",
+ realm);
+ if (!previous->length && !current->length && !next->length)
+ krb5_set_error_message(context, save_ret,
+ "Could not read jwk issuer public key files");
+ if (current->length && current->length == next->length &&
+ memcmp(current->data, next->data, next->length) == 0) {
+ free(next->data);
+ next->data = 0;
+ next->length = 0;
+ }
+ if (current->length && current->length == previous->length &&
+ memcmp(current->data, previous->data, previous->length) == 0) {
+ free(previous->data);
+ previous->data = 0;
+ previous->length = 0;
+ }
+
+ if (previous->data == NULL && current->data == NULL && next->data == NULL)
+ return krb5_set_error_message(context, ENOENT, "No JWKs found"),
+ ENOENT;
+ return 0;
+}
+
+static krb5_error_code
+check_audience(krb5_context context,
+ const char *realm,
+ cjwt_t *jwt,
+ const char * const *audiences,
+ size_t naudiences)
+{
+ size_t i, k;
+
+ if (!jwt->aud) {
+ krb5_set_error_message(context, EACCES, "JWT bearer token has no "
+ "audience");
+ return EACCES;
+ }
+ for (i = 0; i < jwt->aud->count; i++)
+ for (k = 0; k < naudiences; k++)
+ if (strcasecmp(audiences[k], jwt->aud->names[i]) == 0)
+ return 0;
+ krb5_set_error_message(context, EACCES, "JWT bearer token's audience "
+ "does not match any expected audience");
+ return EACCES;
+}
+
+static krb5_error_code
+get_princ(krb5_context context,
+ const char *realm,
+ cjwt_t *jwt,
+ krb5_principal *actual_principal)
+{
+ krb5_error_code ret;
+ const char *force_realm = NULL;
+ const char *domain;
+
+#ifdef HAVE_CJSON
+ if (jwt->private_claims) {
+ cJSON *jval;
+
+ if ((jval = cJSON_GetObjectItem(jwt->private_claims, "authz_sub")))
+ return krb5_parse_name(context, jval->valuestring, actual_principal);
+ }
+#endif
+
+ if (jwt->sub == NULL) {
+ krb5_set_error_message(context, EACCES, "JWT token lacks 'sub' "
+ "(subject name)!");
+ return EACCES;
+ }
+ if ((domain = strchr(jwt->sub, '@'))) {
+ force_realm = get_kv(context, realm, "cjwt_force_realm", ++domain);
+ ret = krb5_parse_name(context, jwt->sub, actual_principal);
+ } else {
+ ret = krb5_parse_name_flags(context, jwt->sub,
+ KRB5_PRINCIPAL_PARSE_NO_REALM,
+ actual_principal);
+ }
+ if (ret)
+ krb5_set_error_message(context, ret, "JWT token 'sub' not a valid "
+ "principal name: %s", jwt->sub);
+ else if (force_realm)
+ ret = krb5_principal_set_realm(context, *actual_principal, realm);
+ else if (domain == NULL)
+ ret = krb5_principal_set_realm(context, *actual_principal, realm);
+ /* else leave the domain as the realm */
+ return ret;
+}
+
+static KRB5_LIB_CALL krb5_error_code
+validate(void *ctx,
+ krb5_context context,
+ const char *realm,
+ const char *token_type,
+ krb5_data *token,
+ const char * const *audiences,
+ size_t naudiences,
+ krb5_boolean *result,
+ krb5_principal *actual_principal,
+ krb5_times *token_times)
+{
+ heim_octet_string jwk_previous;
+ heim_octet_string jwk_current;
+ heim_octet_string jwk_next;
+ cjwt_t *jwt = NULL;
+ char *tokstr = NULL;
+ char *defrealm = NULL;
+ int ret;
+
+ if (strcmp(token_type, "Bearer") != 0)
+ return KRB5_PLUGIN_NO_HANDLE; /* Not us */
+
+ if ((tokstr = calloc(1, token->length + 1)) == NULL)
+ return ENOMEM;
+ memcpy(tokstr, token->data, token->length);
+
+ if (realm == NULL) {
+ ret = krb5_get_default_realm(context, &defrealm);
+ if (ret) {
+ krb5_set_error_message(context, ret, "could not determine default "
+ "realm");
+ free(tokstr);
+ return ret;
+ }
+ realm = defrealm;
+ }
+
+ ret = get_issuer_pubkeys(context, realm, &jwk_previous, &jwk_current,
+ &jwk_next);
+ if (ret) {
+ free(defrealm);
+ free(tokstr);
+ return ret;
+ }
+
+ if (jwk_current.length && jwk_current.data)
+ ret = cjwt_decode(tokstr, 0, &jwt, jwk_current.data,
+ jwk_current.length);
+ if (ret && jwk_next.length && jwk_next.data)
+ ret = cjwt_decode(tokstr, 0, &jwt, jwk_next.data,
+ jwk_next.length);
+ if (ret && jwk_previous.length && jwk_previous.data)
+ ret = cjwt_decode(tokstr, 0, &jwt, jwk_previous.data,
+ jwk_previous.length);
+ free(jwk_previous.data);
+ free(jwk_current.data);
+ free(jwk_next.data);
+ jwk_previous.data = jwk_current.data = jwk_next.data = NULL;
+ free(tokstr);
+ tokstr = NULL;
+ switch (ret) {
+ case 0:
+ if (jwt == NULL) {
+ krb5_set_error_message(context, EINVAL, "JWT validation failed");
+ free(defrealm);
+ return EPERM;
+ }
+ if (jwt->header.alg == alg_none) {
+ krb5_set_error_message(context, EINVAL, "JWT signature algorithm "
+ "not supported");
+ free(defrealm);
+ return EPERM;
+ }
+ break;
+ case -1:
+ krb5_set_error_message(context, EINVAL, "invalid JWT format");
+ free(defrealm);
+ return EINVAL;
+ case -2:
+ krb5_set_error_message(context, EINVAL, "JWT signature validation "
+ "failed (wrong issuer?)");
+ free(defrealm);
+ return EPERM;
+ default:
+ krb5_set_error_message(context, ret, "misc token validation error");
+ free(defrealm);
+ return ret;
+ }
+
+ /* Success; check audience */
+ if ((ret = check_audience(context, realm, jwt, audiences, naudiences))) {
+ cjwt_destroy(&jwt);
+ free(defrealm);
+ return EACCES;
+ }
+
+ /* Success; extract principal name */
+ if ((ret = get_princ(context, realm, jwt, actual_principal)) == 0) {
+ token_times->authtime = jwt->iat.tv_sec;
+ token_times->starttime = jwt->nbf.tv_sec;
+ token_times->endtime = jwt->exp.tv_sec;
+ token_times->renew_till = jwt->exp.tv_sec;
+ *result = TRUE;
+ }
+
+ cjwt_destroy(&jwt);
+ free(defrealm);
+ return ret;
+}
+
+static KRB5_LIB_CALL krb5_error_code
+hcjwt_init(krb5_context context, void **c)
+{
+ *c = NULL;
+ return 0;
+}
+
+static KRB5_LIB_CALL void
+hcjwt_fini(void *c)
+{
+}
+
+static krb5plugin_token_validator_ftable plug_desc =
+ { 1, hcjwt_init, hcjwt_fini, validate };
+
+static krb5plugin_token_validator_ftable *plugs[] = { &plug_desc };
+
+static uintptr_t
+hcjwt_get_instance(const char *libname)
+{
+ if (strcmp(libname, "krb5") == 0)
+ return krb5_get_instance(libname);
+ return 0;
+}
+
+krb5_plugin_load_ft kdc_token_validator_plugin_load;
+
+krb5_error_code KRB5_CALLCONV
+kdc_token_validator_plugin_load(heim_pcontext context,
+ krb5_get_instance_func_t *get_instance,
+ size_t *num_plugins,
+ krb5_plugin_common_ftable_cp **plugins)
+{
+ *get_instance = hcjwt_get_instance;
+ *num_plugins = sizeof(plugs) / sizeof(plugs[0]);
+ *plugins = (krb5_plugin_common_ftable_cp *)plugs;
+ return 0;
+}
diff --git a/third_party/heimdal/kdc/config.c b/third_party/heimdal/kdc/config.c
new file mode 100644
index 0000000..9fd3686
--- /dev/null
+++ b/third_party/heimdal/kdc/config.c
@@ -0,0 +1,329 @@
+/*
+ * Copyright (c) 1997-2007 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "kdc_locl.h"
+#include <getarg.h>
+#include <parse_bytes.h>
+
+#define MAX_REQUEST_MAX 67108864ll /* 64MB, the maximum accepted value of max_request */
+
+struct dbinfo {
+ char *realm;
+ char *dbname;
+ char *mkey_file;
+ struct dbinfo *next;
+};
+
+static char *config_file; /* location of kdc config file */
+
+static int require_preauth = -1; /* 1 == require preauth for all principals */
+static char *max_request_str; /* `max_request' as a string */
+
+static int disable_des = -1;
+
+static int builtin_hdb_flag;
+int testing_flag;
+static int help_flag;
+static int version_flag;
+
+/* Should we enable the HTTP hack? */
+int enable_http = -1;
+
+/* Log over requests to the KDC */
+const char *request_log;
+
+/* A string describing on what ports to listen */
+const char *port_str;
+
+krb5_addresses explicit_addresses;
+
+size_t max_request_udp;
+size_t max_request_tcp;
+
+
+static struct getarg_strings addresses_str; /* addresses to listen on */
+
+char *runas_string;
+char *chroot_string;
+
+
+static struct getargs args[] = {
+ {
+ "config-file", 'c', arg_string, &config_file,
+ "location of config file", "file"
+ },
+ {
+ "require-preauth", 'p', arg_negative_flag, &require_preauth,
+ "don't require pa-data in as-reqs", NULL
+ },
+ {
+ "max-request", 0, arg_string, &max_request_str,
+ "max size for a kdc-request", "size"
+ },
+ { "enable-http", 'H', arg_flag, &enable_http, "turn on HTTP support",
+ NULL },
+ { "ports", 'P', arg_string, rk_UNCONST(&port_str),
+ "ports to listen to", "portspec"
+ },
+ {
+ "detach", 0 , arg_flag, &detach_from_console,
+ "detach from console", NULL
+ },
+ {
+ "daemon-child", 0 , arg_flag, &daemon_child,
+ "private argument, do not use", NULL
+ },
+#ifdef __APPLE__
+ {
+ "bonjour", 0 , arg_flag, &do_bonjour,
+ "private argument, do not use", NULL
+ },
+#endif
+ { "addresses", 0, arg_strings, &addresses_str,
+ "addresses to listen on", "list of addresses" },
+ { "disable-des", 0, arg_flag, &disable_des,
+ "disable DES", NULL },
+ { "builtin-hdb", 0, arg_flag, &builtin_hdb_flag,
+ "list builtin hdb backends", NULL},
+ { "runas-user", 0, arg_string, &runas_string,
+ "run as this user when connected to network", NULL
+ },
+ { "chroot", 0, arg_string, &chroot_string,
+ "chroot directory to run in", NULL
+ },
+ { "testing", 0, arg_flag, &testing_flag, NULL, NULL },
+ { "help", 'h', arg_flag, &help_flag, NULL, NULL },
+ { "version", 'v', arg_flag, &version_flag, NULL, NULL }
+};
+
+static int num_args = sizeof(args) / sizeof(args[0]);
+
+static void
+usage(int ret)
+{
+ arg_printusage (args, num_args, NULL, "");
+ exit (ret);
+}
+
+static void
+add_one_address (krb5_context context, const char *str, int first)
+{
+ krb5_error_code ret;
+ krb5_addresses tmp;
+
+ ret = krb5_parse_address (context, str, &tmp);
+ if (ret)
+ krb5_err (context, 1, ret, "parse_address `%s'", str);
+ if (first)
+ krb5_copy_addresses(context, &tmp, &explicit_addresses);
+ else
+ krb5_append_addresses(context, &explicit_addresses, &tmp);
+ krb5_free_addresses (context, &tmp);
+}
+
+krb5_kdc_configuration *
+configure(krb5_context context, int argc, char **argv, int *optidx)
+{
+ krb5_kdc_configuration *config;
+ krb5_error_code ret;
+
+ const char *p;
+
+ *optidx = 0;
+
+ while (getarg(args, num_args, argc, argv, optidx))
+ warnx("error at argument `%s'", argv[*optidx]);
+
+ if (help_flag)
+ usage (0);
+
+ if (version_flag) {
+ print_version(NULL);
+ exit(0);
+ }
+
+ if (builtin_hdb_flag) {
+ char *list;
+ ret = hdb_list_builtin(context, &list);
+ if (ret)
+ krb5_err(context, 1, ret, "listing builtin hdb backends");
+ printf("builtin hdb backends: %s\n", list);
+ free(list);
+ exit(0);
+ }
+
+ if(detach_from_console == -1)
+ detach_from_console = krb5_config_get_bool_default(context, NULL,
+ FALSE,
+ "kdc",
+ "detach", NULL);
+
+ if (detach_from_console && daemon_child == -1)
+ daemon_child = roken_detach_prep(argc, argv, "--daemon-child");
+
+ {
+ char **files;
+ int aret;
+
+ if (config_file == NULL) {
+ aret = asprintf(&config_file, "%s/kdc.conf", hdb_db_dir(context));
+ if (aret == -1 || config_file == NULL)
+ errx(1, "out of memory");
+ }
+
+ ret = krb5_prepend_config_files_default(config_file, &files);
+ if (ret)
+ krb5_err(context, 1, ret, "getting configuration files");
+
+ ret = krb5_set_config_files(context, files);
+ krb5_free_config_files(files);
+ if(ret)
+ krb5_err(context, 1, ret, "reading configuration files");
+ }
+
+ ret = krb5_kdc_get_config(context, &config);
+ if (ret)
+ krb5_err(context, 1, ret, "krb5_kdc_default_config");
+
+ kdc_openlog(context, "kdc", config);
+
+ ret = krb5_kdc_set_dbinfo(context, config);
+ if (ret)
+ krb5_err(context, 1, ret, "krb5_kdc_set_dbinfo");
+
+ if (max_request_str) {
+ int64_t bytes;
+
+ if ((bytes = parse_bytes(max_request_str, NULL)) < 0)
+ krb5_errx(context, 1, "--max-request must be non-negative");
+
+ if (bytes > MAX_REQUEST_MAX)
+ krb5_errx(context, 1, "--max-request size is too big "
+ "(must be smaller than %lld)", MAX_REQUEST_MAX);
+
+ max_request_tcp = max_request_udp = bytes;
+ }
+
+ if(max_request_tcp == 0){
+ p = krb5_config_get_string (context,
+ NULL,
+ "kdc",
+ "max-request",
+ NULL);
+ if (p) {
+ int64_t bytes;
+
+ if ((bytes = parse_bytes(max_request_str, NULL)) < 0)
+ krb5_errx(context, 1, "[kdc] max-request must be non-negative");
+
+ if (bytes > MAX_REQUEST_MAX)
+ krb5_errx(context, 1, "[kdc] max-request size is too big "
+ "(must be smaller than %lld)", MAX_REQUEST_MAX);
+
+ max_request_tcp = max_request_udp = bytes;
+ }
+ }
+
+ if(require_preauth != -1)
+ config->require_preauth = require_preauth;
+
+ if(port_str == NULL){
+ p = krb5_config_get_string(context, NULL, "kdc", "ports", NULL);
+ if (p != NULL)
+ port_str = strdup(p);
+ }
+
+ explicit_addresses.len = 0;
+
+ if (addresses_str.num_strings) {
+ int i;
+
+ for (i = 0; i < addresses_str.num_strings; ++i)
+ add_one_address (context, addresses_str.strings[i], i == 0);
+ free_getarg_strings (&addresses_str);
+ } else {
+ char **foo = krb5_config_get_strings (context, NULL,
+ "kdc", "addresses", NULL);
+
+ if (foo != NULL) {
+ add_one_address (context, *foo++, TRUE);
+ while (*foo)
+ add_one_address (context, *foo++, FALSE);
+ }
+ }
+
+ if(enable_http == -1)
+ enable_http = krb5_config_get_bool(context, NULL, "kdc",
+ "enable-http", NULL);
+
+ if(request_log == NULL)
+ request_log = krb5_config_get_string(context, NULL,
+ "kdc",
+ "kdc-request-log",
+ NULL);
+
+ if (krb5_config_get_string(context, NULL, "kdc",
+ "enforce-transited-policy", NULL))
+ krb5_errx(context, 1, "enforce-transited-policy deprecated, "
+ "use [kdc]transited-policy instead");
+
+ if(max_request_tcp == 0)
+ max_request_tcp = 64 * 1024;
+ if(max_request_udp == 0)
+ max_request_udp = 64 * 1024;
+
+ if (port_str == NULL)
+ port_str = "+";
+
+ if(disable_des == -1)
+ disable_des = krb5_config_get_bool_default(context, NULL,
+ FALSE,
+ "kdc",
+ "disable-des", NULL);
+ if(disable_des) {
+ krb5_enctype_disable(context, ETYPE_DES_CBC_CRC);
+ krb5_enctype_disable(context, ETYPE_DES_CBC_MD4);
+ krb5_enctype_disable(context, ETYPE_DES_CBC_MD5);
+ krb5_enctype_disable(context, ETYPE_DES_CBC_NONE);
+ krb5_enctype_disable(context, ETYPE_DES_CFB64_NONE);
+ krb5_enctype_disable(context, ETYPE_DES_PCBC_NONE);
+ }
+
+ krb5_kdc_plugin_init(context);
+
+ krb5_kdc_pkinit_config(context, config);
+
+ return config;
+}
diff --git a/third_party/heimdal/kdc/connect.c b/third_party/heimdal/kdc/connect.c
new file mode 100644
index 0000000..ba8c8ad
--- /dev/null
+++ b/third_party/heimdal/kdc/connect.c
@@ -0,0 +1,1319 @@
+/*
+ * Copyright (c) 1997-2005 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "kdc_locl.h"
+
+/*
+ * a tuple describing on what to listen
+ */
+
+struct port_desc{
+ int family;
+ int type;
+ int port;
+};
+
+/* the current ones */
+
+static struct port_desc *ports;
+static size_t num_ports;
+static pid_t bonjour_pid = -1;
+
+/*
+ * add `family, port, protocol' to the list with duplicate suppresion.
+ */
+
+static void
+add_port(krb5_context context,
+ int family, int port, const char *protocol)
+{
+ int type;
+ size_t i;
+
+ if(strcmp(protocol, "udp") == 0)
+ type = SOCK_DGRAM;
+ else if(strcmp(protocol, "tcp") == 0)
+ type = SOCK_STREAM;
+ else
+ return;
+ for(i = 0; i < num_ports; i++){
+ if(ports[i].type == type
+ && ports[i].port == port
+ && ports[i].family == family)
+ return;
+ }
+ ports = realloc(ports, (num_ports + 1) * sizeof(*ports));
+ if (ports == NULL)
+ krb5_err (context, 1, errno, "realloc");
+ ports[num_ports].family = family;
+ ports[num_ports].type = type;
+ ports[num_ports].port = port;
+ num_ports++;
+}
+
+/*
+ * add a triple but with service -> port lookup
+ * (this prints warnings for stuff that does not exist)
+ */
+
+static void
+add_port_service(krb5_context context,
+ int family, const char *service, int port,
+ const char *protocol)
+{
+ port = krb5_getportbyname (context, service, protocol, port);
+ add_port (context, family, port, protocol);
+}
+
+/*
+ * add the port with service -> port lookup or string -> number
+ * (no warning is printed)
+ */
+
+static void
+add_port_string (krb5_context context,
+ int family, const char *str, const char *protocol)
+{
+ struct servent *sp;
+ int port;
+
+ sp = roken_getservbyname (str, protocol);
+ if (sp != NULL) {
+ port = sp->s_port;
+ } else {
+ char *end;
+
+ port = htons(strtol(str, &end, 0));
+ if (end == str)
+ return;
+ }
+ add_port (context, family, port, protocol);
+}
+
+/*
+ * add the standard collection of ports for `family'
+ */
+
+static void
+add_standard_ports (krb5_context context,
+ krb5_kdc_configuration *config,
+ int family)
+{
+ add_port_service(context, family, "kerberos", 88, "udp");
+ add_port_service(context, family, "kerberos", 88, "tcp");
+ add_port_service(context, family, "kerberos-sec", 88, "udp");
+ add_port_service(context, family, "kerberos-sec", 88, "tcp");
+ if(enable_http)
+ add_port_service(context, family, "http", 80, "tcp");
+ if(config->enable_kx509) {
+ add_port_service(context, family, "kca_service", 9878, "udp");
+ add_port_service(context, family, "kca_service", 9878, "tcp");
+ }
+
+}
+
+/*
+ * parse the set of space-delimited ports in `str' and add them.
+ * "+" => all the standard ones
+ * otherwise it's port|service[/protocol]
+ */
+
+static void
+parse_ports(krb5_context context,
+ krb5_kdc_configuration *config,
+ const char *str)
+{
+ char *pos = NULL;
+ char *p;
+ char *str_copy = strdup (str);
+
+ p = strtok_r(str_copy, " \t", &pos);
+ while(p != NULL) {
+ if(strcmp(p, "+") == 0) {
+#ifdef HAVE_IPV6
+ add_standard_ports(context, config, AF_INET6);
+#endif
+ add_standard_ports(context, config, AF_INET);
+ } else {
+ char *q = strchr(p, '/');
+ if(q){
+ *q++ = 0;
+#ifdef HAVE_IPV6
+ add_port_string(context, AF_INET6, p, q);
+#endif
+ add_port_string(context, AF_INET, p, q);
+ }else {
+#ifdef HAVE_IPV6
+ add_port_string(context, AF_INET6, p, "udp");
+ add_port_string(context, AF_INET6, p, "tcp");
+#endif
+ add_port_string(context, AF_INET, p, "udp");
+ add_port_string(context, AF_INET, p, "tcp");
+ }
+ }
+
+ p = strtok_r(NULL, " \t", &pos);
+ }
+ free (str_copy);
+}
+
+/*
+ * every socket we listen on
+ */
+
+struct descr {
+ krb5_socket_t s;
+ int type;
+ int port;
+ unsigned char *buf;
+ size_t size;
+ size_t len;
+ time_t timeout;
+ struct sockaddr_storage __ss;
+ struct sockaddr *sa;
+ socklen_t sock_len;
+ char addr_string[128];
+};
+
+static void
+init_descr(struct descr *d)
+{
+ memset(d, 0, sizeof(*d));
+ d->sa = (struct sockaddr *)&d->__ss;
+ d->s = rk_INVALID_SOCKET;
+}
+
+/*
+ * re-initialize all `n' ->sa in `d'.
+ */
+
+static void
+reinit_descrs (struct descr *d, int n)
+{
+ int i;
+
+ for (i = 0; i < n; ++i)
+ d[i].sa = (struct sockaddr *)&d[i].__ss;
+}
+
+/*
+ * Create the socket (family, type, port) in `d'
+ */
+
+static void
+init_socket(krb5_context context,
+ krb5_kdc_configuration *config,
+ struct descr *d, krb5_address *a, int family, int type, int port)
+{
+ krb5_error_code ret;
+ struct sockaddr_storage __ss;
+ struct sockaddr *sa = (struct sockaddr *)&__ss;
+ krb5_socklen_t sa_size = sizeof(__ss);
+
+ init_descr (d);
+
+ ret = krb5_addr2sockaddr (context, a, sa, &sa_size, port);
+ if (ret) {
+ krb5_warn(context, ret, "krb5_addr2sockaddr");
+ rk_closesocket(d->s);
+ d->s = rk_INVALID_SOCKET;
+ return;
+ }
+
+ if (sa->sa_family != family)
+ return;
+
+ d->s = socket(family, type, 0);
+ if(rk_IS_BAD_SOCKET(d->s)){
+ krb5_warn(context, errno, "socket(%d, %d, 0)", family, type);
+ d->s = rk_INVALID_SOCKET;
+ return;
+ }
+ rk_cloexec(d->s);
+#if defined(HAVE_SETSOCKOPT) && defined(SOL_SOCKET) && defined(SO_REUSEADDR)
+ {
+ int one = 1;
+ (void) setsockopt(d->s, SOL_SOCKET, SO_REUSEADDR, (void *)&one,
+ sizeof(one));
+ }
+#endif
+ d->type = type;
+ d->port = port;
+
+ socket_set_nonblocking(d->s, 1);
+
+ if(rk_IS_SOCKET_ERROR(bind(d->s, sa, sa_size))){
+ char a_str[256];
+ size_t len;
+
+ krb5_print_address (a, a_str, sizeof(a_str), &len);
+ krb5_warn(context, errno, "bind %s/%d", a_str, ntohs(port));
+ rk_closesocket(d->s);
+ d->s = rk_INVALID_SOCKET;
+ return;
+ }
+ if(type == SOCK_STREAM && rk_IS_SOCKET_ERROR(listen(d->s, SOMAXCONN))){
+ char a_str[256];
+ size_t len;
+
+ krb5_print_address (a, a_str, sizeof(a_str), &len);
+ krb5_warn(context, errno, "listen %s/%d", a_str, ntohs(port));
+ rk_closesocket(d->s);
+ d->s = rk_INVALID_SOCKET;
+ return;
+ }
+ socket_set_keepalive(d->s, 1);
+}
+
+/*
+ * Allocate descriptors for all the sockets that we should listen on
+ * and return the number of them.
+ */
+
+static int
+init_sockets(krb5_context context,
+ krb5_kdc_configuration *config,
+ struct descr **desc)
+{
+ krb5_error_code ret;
+ size_t i, j;
+ struct descr *d;
+ int num = 0;
+ krb5_addresses addresses;
+
+ if (explicit_addresses.len) {
+ addresses = explicit_addresses;
+ } else {
+ ret = krb5_get_all_server_addrs (context, &addresses);
+ if (ret)
+ krb5_err (context, 1, ret, "krb5_get_all_server_addrs");
+ }
+ parse_ports(context, config, port_str);
+ d = malloc(addresses.len * num_ports * sizeof(*d));
+ if (d == NULL)
+ krb5_errx(context, 1, "malloc(%lu) failed",
+ (unsigned long)num_ports * sizeof(*d));
+
+ for (i = 0; i < num_ports; i++){
+ for (j = 0; j < addresses.len; ++j) {
+ init_socket(context, config, &d[num], &addresses.val[j],
+ ports[i].family, ports[i].type, ports[i].port);
+ if(d[num].s != rk_INVALID_SOCKET){
+ char a_str[80];
+ size_t len;
+
+ krb5_print_address (&addresses.val[j], a_str,
+ sizeof(a_str), &len);
+
+ kdc_log(context, config, 3, "listening on %s port %u/%s",
+ a_str,
+ ntohs(ports[i].port),
+ (ports[i].type == SOCK_STREAM) ? "tcp" : "udp");
+ /* XXX */
+ num++;
+ }
+ }
+ }
+ krb5_free_addresses (context, &addresses);
+ d = realloc(d, num * sizeof(*d));
+ if (d == NULL && num != 0)
+ krb5_errx(context, 1, "realloc(%lu) failed",
+ (unsigned long)num * sizeof(*d));
+ reinit_descrs (d, num);
+ *desc = d;
+ return num;
+}
+
+/*
+ *
+ */
+
+static const char *
+descr_type(struct descr *d)
+{
+ if (d->type == SOCK_DGRAM)
+ return "udp";
+ else if (d->type == SOCK_STREAM)
+ return "tcp";
+ return "unknown";
+}
+
+static void
+addr_to_string(krb5_context context,
+ struct sockaddr *addr, size_t addr_len, char *str, size_t len)
+{
+ krb5_address a;
+ if(krb5_sockaddr2address(context, addr, &a) == 0) {
+ if(krb5_print_address(&a, str, len, &len) == 0) {
+ krb5_free_address(context, &a);
+ return;
+ }
+ krb5_free_address(context, &a);
+ }
+ snprintf(str, len, "<family=%d>", addr->sa_family);
+}
+
+/*
+ *
+ */
+
+static void
+send_reply(krb5_context context,
+ krb5_kdc_configuration *config,
+ krb5_boolean prependlength,
+ struct descr *d,
+ krb5_data *reply)
+{
+ kdc_log(context, config, 4,
+ "sending %lu bytes to %s", (unsigned long)reply->length,
+ d->addr_string);
+ if(prependlength){
+ unsigned char l[4];
+ l[0] = (reply->length >> 24) & 0xff;
+ l[1] = (reply->length >> 16) & 0xff;
+ l[2] = (reply->length >> 8) & 0xff;
+ l[3] = reply->length & 0xff;
+ if(rk_IS_SOCKET_ERROR(sendto(d->s, l, sizeof(l), 0, d->sa, d->sock_len))) {
+ kdc_log (context, config,
+ 1, "sendto(%s): %s", d->addr_string,
+ strerror(rk_SOCK_ERRNO));
+ return;
+ }
+ }
+ if(rk_IS_SOCKET_ERROR(sendto(d->s, reply->data, reply->length, 0, d->sa, d->sock_len))) {
+ kdc_log (context, config, 1, "sendto(%s): %s", d->addr_string,
+ strerror(rk_SOCK_ERRNO));
+ return;
+ }
+}
+
+/*
+ * Handle the request in `buf, len' to socket `d'
+ */
+
+static void
+do_request(krb5_context context,
+ krb5_kdc_configuration *config,
+ void *buf, size_t len, krb5_boolean prependlength,
+ struct descr *d)
+{
+ krb5_error_code ret;
+ krb5_data reply;
+ int datagram_reply = (d->type == SOCK_DGRAM);
+
+ krb5_kdc_update_time(NULL);
+
+ krb5_data_zero(&reply);
+ ret = krb5_kdc_process_request(context, config,
+ buf, len, &reply, &prependlength,
+ d->addr_string, d->sa,
+ datagram_reply);
+ if(request_log)
+ krb5_kdc_save_request(context, request_log, buf, len, &reply, d->sa);
+ if(reply.length){
+ send_reply(context, config, prependlength, d, &reply);
+ krb5_data_free(&reply);
+ }
+ if(ret)
+ kdc_log(context, config, 1,
+ "Failed processing %lu byte request from %s",
+ (unsigned long)len, d->addr_string);
+}
+
+/*
+ * Handle incoming data to the UDP socket in `d'
+ */
+
+static void
+handle_udp(krb5_context context,
+ krb5_kdc_configuration *config,
+ struct descr *d)
+{
+ unsigned char *buf;
+ ssize_t n;
+
+ buf = malloc(max_request_udp);
+ if (buf == NULL){
+ kdc_log(context, config, 1, "Failed to allocate %lu bytes",
+ (unsigned long)max_request_udp);
+ return;
+ }
+
+ d->sock_len = sizeof(d->__ss);
+ n = recvfrom(d->s, buf, max_request_udp, 0, d->sa, &d->sock_len);
+ if (rk_IS_SOCKET_ERROR(n)) {
+ if (rk_SOCK_ERRNO != EAGAIN && rk_SOCK_ERRNO != EINTR)
+ krb5_warn(context, rk_SOCK_ERRNO, "recvfrom");
+ } else {
+ addr_to_string (context, d->sa, d->sock_len,
+ d->addr_string, sizeof(d->addr_string));
+ if ((size_t)n == max_request_udp) {
+ krb5_data data;
+ krb5_warn(context, errno,
+ "recvfrom: truncated packet from %s, asking for TCP",
+ d->addr_string);
+ krb5_mk_error(context,
+ KRB5KRB_ERR_RESPONSE_TOO_BIG,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ &data);
+ send_reply(context, config, FALSE, d, &data);
+ krb5_data_free(&data);
+ } else {
+ do_request(context, config, buf, n, FALSE, d);
+ }
+ }
+ free (buf);
+}
+
+static void
+clear_descr(struct descr *d)
+{
+ if(d->buf)
+ memset(d->buf, 0, d->size);
+ d->len = 0;
+ if(d->s != rk_INVALID_SOCKET)
+ rk_closesocket(d->s);
+ d->s = rk_INVALID_SOCKET;
+}
+
+
+/* remove HTTP %-quoting from buf */
+static int
+de_http(char *buf)
+{
+ unsigned char *p, *q;
+ unsigned int x;
+
+ for (p = q = (unsigned char *)buf; *p; p++, q++) {
+ if (*p == '%') {
+ if (!(isxdigit(p[1]) && isxdigit(p[2])))
+ return -1;
+
+ if (sscanf((char *)p + 1, "%2x", &x) != 1)
+ return -1;
+
+ *q = x;
+ p += 2;
+ } else {
+ *q = *p;
+ }
+ }
+ *q = '\0';
+ return 0;
+}
+
+#define TCP_TIMEOUT 4
+
+/*
+ * accept a new TCP connection on `d[parent]' and store it in `d[child]'
+ */
+
+static void
+add_new_tcp (krb5_context context,
+ krb5_kdc_configuration *config,
+ struct descr *d, int parent, int child)
+{
+ krb5_socket_t s;
+
+ if (child == -1)
+ return;
+
+ d[child].sock_len = sizeof(d[child].__ss);
+ s = accept(d[parent].s, d[child].sa, &d[child].sock_len);
+ if(rk_IS_BAD_SOCKET(s)) {
+ if (rk_SOCK_ERRNO != EAGAIN && rk_SOCK_ERRNO != EINTR)
+ krb5_warn(context, rk_SOCK_ERRNO, "accept");
+ return;
+ }
+
+#ifdef FD_SETSIZE
+ if (s >= FD_SETSIZE) {
+ krb5_warnx(context, "socket FD too large");
+ rk_closesocket (s);
+ return;
+ }
+#endif
+
+ d[child].s = s;
+ d[child].timeout = time(NULL) + TCP_TIMEOUT;
+ d[child].type = SOCK_STREAM;
+ addr_to_string (context,
+ d[child].sa, d[child].sock_len,
+ d[child].addr_string, sizeof(d[child].addr_string));
+}
+
+/*
+ * Grow `d' to handle at least `n'.
+ * Return != 0 if fails
+ */
+
+static int
+grow_descr (krb5_context context,
+ krb5_kdc_configuration *config,
+ struct descr *d, size_t n)
+{
+ if (d->size - d->len < n) {
+ unsigned char *tmp;
+ size_t grow;
+
+ grow = max(1024, d->len + n);
+ if (d->size + grow > max_request_tcp) {
+ kdc_log(context, config, 2, "Request exceeds max request size (%lu bytes).",
+ (unsigned long)d->size + grow);
+ clear_descr(d);
+ return -1;
+ }
+ tmp = realloc (d->buf, d->size + grow);
+ if (tmp == NULL) {
+ kdc_log(context, config, 1, "Failed to re-allocate %lu bytes.",
+ (unsigned long)d->size + grow);
+ clear_descr(d);
+ return -1;
+ }
+ d->size += grow;
+ d->buf = tmp;
+ }
+ return 0;
+}
+
+/*
+ * Try to handle the TCP data at `d->buf, d->len'.
+ * Return -1 if failed, 0 if succesful, and 1 if data is complete.
+ */
+
+static int
+handle_vanilla_tcp (krb5_context context,
+ krb5_kdc_configuration *config,
+ struct descr *d)
+{
+ krb5_error_code ret;
+ krb5_storage *sp;
+ uint32_t len;
+
+ if (d->len < 4)
+ return 0;
+ sp = krb5_storage_from_mem(d->buf, d->len);
+ if (sp == NULL) {
+ kdc_log (context, config, 1, "krb5_storage_from_mem failed");
+ return -1;
+ }
+ ret = krb5_ret_uint32(sp, &len);
+ if (ret) {
+ kdc_log(context, config, 4, "failed to read request length");
+ return -1;
+ }
+ krb5_storage_free(sp);
+ if(d->len - 4 >= len) {
+ memmove(d->buf, d->buf + 4, d->len - 4);
+ d->len -= 4;
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Try to handle the TCP/HTTP data at `d->buf, d->len'.
+ * Return -1 if failed, 0 if succesful, and 1 if data is complete.
+ */
+
+static int
+handle_http_tcp (krb5_context context,
+ krb5_kdc_configuration *config,
+ struct descr *d)
+{
+ char *s, *p, *t;
+ void *data;
+ char *proto;
+ int len;
+
+ s = (char *)d->buf;
+
+ /* If its a multi line query, truncate off the first line */
+ p = strstr(s, "\r\n");
+ if (p)
+ *p = 0;
+
+ p = NULL;
+ t = strtok_r(s, " \t", &p);
+ if (t == NULL) {
+ kdc_log(context, config, 2,
+ "Missing HTTP operand (GET) request from %s", d->addr_string);
+ return -1;
+ }
+
+ t = strtok_r(NULL, " \t", &p);
+ if(t == NULL) {
+ kdc_log(context, config, 2,
+ "Missing HTTP GET data in request from %s", d->addr_string);
+ return -1;
+ }
+
+ data = malloc(strlen(t));
+ if (data == NULL) {
+ kdc_log(context, config, 1, "Failed to allocate %lu bytes",
+ (unsigned long)strlen(t));
+ return -1;
+ }
+ if(*t == '/')
+ t++;
+ if(de_http(t) != 0) {
+ kdc_log(context, config, 2, "Malformed HTTP request from %s", d->addr_string);
+ kdc_log(context, config, 4, "HTTP request: %s", t);
+ free(data);
+ return -1;
+ }
+ proto = strtok_r(NULL, " \t", &p);
+ if (proto == NULL) {
+ kdc_log(context, config, 2, "Malformed HTTP request from %s", d->addr_string);
+ free(data);
+ return -1;
+ }
+ len = rk_base64_decode(t, data);
+ if(len <= 0){
+ const char *msg =
+ " 404 Not found\r\n"
+ "Server: Heimdal/" VERSION "\r\n"
+ "Cache-Control: no-cache\r\n"
+ "Pragma: no-cache\r\n"
+ "Content-type: text/html\r\n"
+ "Content-transfer-encoding: 8bit\r\n\r\n"
+ "<TITLE>404 Not found</TITLE>\r\n"
+ "<H1>404 Not found</H1>\r\n"
+ "That page doesn't exist, maybe you are looking for "
+ "<A HREF=\"http://www.h5l.org/\">Heimdal</A>?\r\n";
+ kdc_log(context, config, 2, "HTTP request from %s is non KDC request", d->addr_string);
+ kdc_log(context, config, 4, "HTTP request: %s", t);
+ free(data);
+ if (rk_IS_SOCKET_ERROR(send(d->s, proto, strlen(proto), 0))) {
+ kdc_log(context, config, 1, "HTTP write failed: %s: %s",
+ d->addr_string, strerror(rk_SOCK_ERRNO));
+ return -1;
+ }
+ if (rk_IS_SOCKET_ERROR(send(d->s, msg, strlen(msg), 0))) {
+ kdc_log(context, config, 1, "HTTP write failed: %s: %s",
+ d->addr_string, strerror(rk_SOCK_ERRNO));
+ return -1;
+ }
+ return -1;
+ }
+ {
+ const char *msg =
+ " 200 OK\r\n"
+ "Server: Heimdal/" VERSION "\r\n"
+ "Cache-Control: no-cache\r\n"
+ "Pragma: no-cache\r\n"
+ "Content-type: application/octet-stream\r\n"
+ "Content-transfer-encoding: binary\r\n\r\n";
+ if (rk_IS_SOCKET_ERROR(send(d->s, proto, strlen(proto), 0))) {
+ free(data);
+ kdc_log(context, config, 1, "HTTP write failed: %s: %s",
+ d->addr_string, strerror(rk_SOCK_ERRNO));
+ return -1;
+ }
+ if (rk_IS_SOCKET_ERROR(send(d->s, msg, strlen(msg), 0))) {
+ free(data);
+ kdc_log(context, config, 1, "HTTP write failed: %s: %s",
+ d->addr_string, strerror(rk_SOCK_ERRNO));
+ return -1;
+ }
+ }
+ if ((size_t)len > d->len)
+ len = d->len;
+ memcpy(d->buf, data, len);
+ d->len = len;
+ free(data);
+ return 1;
+}
+
+static int
+http1_request_taste(const unsigned char *req, size_t len)
+{
+ return !!((len >= sizeof("GET ") - 1 &&
+ memcmp(req, "GET ", sizeof("GET ") - 1) == 0) ||
+ (len >= sizeof("HEAD ") - 1 &&
+ memcmp(req, "HEAD ", sizeof("HEAD ") - 1) == 0));
+}
+
+static int
+http1_request_is_complete(const unsigned char *req, size_t len)
+{
+
+ return http1_request_taste(req, len) &&
+ memmem(req, len, "\r\n\r\n", sizeof("\r\n\r\n") - 4) != NULL;
+
+ /*
+ * For POST (the MSFT variant of this protocol) we'll need something like
+ * this (plus check for Content-Length/Transfer-Encoding):
+ *
+ * const unsigned char *body;
+ * if ((body = memmem(req, len, "\r\n\r\n", sizeof("\r\n\r\n") - 4)) == NULL)
+ * return 0;
+ * body += sizeof("\r\n\r\n") - 4;
+ * len -= (body - req);
+ * return memmem(body, len, "\r\n\r\n", sizeof("\r\n\r\n") - 4) != NULL;
+ *
+ * Since the POST-based variant runs over HTTPS, we'll probably implement
+ * that in a proxy instead of here.
+ */
+}
+
+/*
+ * Handle incoming data to the TCP socket in `d[index]'
+ */
+
+static void
+handle_tcp(krb5_context context,
+ krb5_kdc_configuration *config,
+ struct descr *d, int idx, int min_free)
+{
+ unsigned char buf[1024];
+ int n;
+ int ret = 0;
+
+ if (d[idx].timeout == 0) {
+ add_new_tcp (context, config, d, idx, min_free);
+ return;
+ }
+
+ n = recvfrom(d[idx].s, buf, sizeof(buf), 0, NULL, NULL);
+ if(rk_IS_SOCKET_ERROR(n)){
+ krb5_warn(context, rk_SOCK_ERRNO, "recvfrom failed from %s to %s/%d",
+ d[idx].addr_string, descr_type(d + idx),
+ ntohs(d[idx].port));
+ return;
+ } else if (n == 0) {
+ krb5_warnx(context, "connection closed before end of data after %lu "
+ "bytes from %s to %s/%d", (unsigned long)d[idx].len,
+ d[idx].addr_string, descr_type(d + idx),
+ ntohs(d[idx].port));
+ clear_descr (d + idx);
+ return;
+ }
+ if (grow_descr (context, config, &d[idx], n))
+ return;
+ memcpy(d[idx].buf + d[idx].len, buf, n);
+ d[idx].len += n;
+ if(d[idx].len > 4 && d[idx].buf[0] == 0) {
+ ret = handle_vanilla_tcp (context, config, &d[idx]);
+ } else if (enable_http &&
+ http1_request_taste(d[idx].buf, d[idx].len)) {
+
+ if (http1_request_is_complete(d[idx].buf, d[idx].len)) {
+ /* NUL-terminate at the request header ending \r\n\r\n */
+ d[idx].buf[d[idx].len - 4] = '\0';
+ ret = handle_http_tcp (context, config, &d[idx]);
+ }
+ } else if (d[idx].len > 4) {
+ kdc_log (context, config,
+ 2, "TCP data of strange type from %s to %s/%d",
+ d[idx].addr_string, descr_type(d + idx),
+ ntohs(d[idx].port));
+ if (d[idx].buf[0] & 0x80) {
+ krb5_data reply;
+
+ kdc_log (context, config, 2, "TCP extension not supported");
+
+ ret = krb5_mk_error(context,
+ KRB5KRB_ERR_FIELD_TOOLONG,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ &reply);
+ if (ret == 0) {
+ send_reply(context, config, TRUE, d + idx, &reply);
+ krb5_data_free(&reply);
+ }
+ }
+ clear_descr(d + idx);
+ return;
+ }
+
+ /*
+ * ret == 0 -> not enough of request buffered -> wait for more
+ * ret == 1 -> go ahead and perform the request
+ * ret != 0 (really, < 0) -> error, probably ENOMEM, close connection
+ */
+ if (ret == 1)
+ do_request(context, config,
+ d[idx].buf, d[idx].len, TRUE, &d[idx]);
+
+ /*
+ * Note: this means we don't keep the connection open even where we
+ * the protocol permits it.
+ */
+ if (ret != 0)
+ clear_descr(d + idx);
+}
+
+#ifdef HAVE_FORK
+static void
+handle_islive(int fd)
+{
+ char buf;
+ int ret;
+
+ ret = read(fd, &buf, 1);
+ if (ret != 1)
+ exit_flag = -1;
+}
+#endif
+
+static krb5_boolean
+realloc_descrs(struct descr **d, unsigned int *ndescr)
+{
+ struct descr *tmp;
+ size_t i;
+
+ tmp = realloc(*d, (*ndescr + 4) * sizeof(**d));
+ if(tmp == NULL)
+ return FALSE;
+
+ *d = tmp;
+ reinit_descrs (*d, *ndescr);
+ memset(*d + *ndescr, 0, 4 * sizeof(**d));
+ for(i = *ndescr; i < *ndescr + 4; i++)
+ init_descr (*d + i);
+
+ *ndescr += 4;
+
+ return TRUE;
+}
+
+static int
+next_min_free(krb5_context context, struct descr **d, unsigned int *ndescr)
+{
+ size_t i;
+ int min_free;
+
+ for(i = 0; i < *ndescr; i++) {
+ int s = (*d + i)->s;
+ if(rk_IS_BAD_SOCKET(s))
+ return i;
+ }
+
+ min_free = *ndescr;
+ if(!realloc_descrs(d, ndescr)) {
+ min_free = -1;
+ krb5_warnx(context, "No memory");
+ }
+
+ return min_free;
+}
+
+static void
+loop(krb5_context context, krb5_kdc_configuration *config,
+ struct descr **dp, unsigned int *ndescrp, int islive)
+{
+ struct descr *d = *dp;
+ unsigned int ndescr = *ndescrp;
+
+ while (exit_flag == 0) {
+ struct timeval tmout;
+ fd_set fds;
+ int min_free = -1;
+ int max_fd = 0;
+ size_t i;
+
+ FD_ZERO(&fds);
+ if (islive > -1) {
+ FD_SET(islive, &fds);
+ max_fd = islive;
+ }
+ for (i = 0; i < ndescr; i++) {
+ if (!rk_IS_BAD_SOCKET(d[i].s)) {
+ if (d[i].type == SOCK_STREAM &&
+ d[i].timeout && d[i].timeout < time(NULL)) {
+ kdc_log(context, config, 2,
+ "TCP-connection from %s expired after %lu bytes",
+ d[i].addr_string, (unsigned long)d[i].len);
+ clear_descr(&d[i]);
+ continue;
+ }
+#ifndef NO_LIMIT_FD_SETSIZE
+ if (max_fd < d[i].s)
+ max_fd = d[i].s;
+#ifdef FD_SETSIZE
+ if (max_fd >= FD_SETSIZE)
+ krb5_errx(context, 1, "fd too large");
+#endif
+#endif
+ FD_SET(d[i].s, &fds);
+ }
+ }
+
+ tmout.tv_sec = TCP_TIMEOUT;
+ tmout.tv_usec = 0;
+ switch(select(max_fd + 1, &fds, 0, 0, &tmout)){
+ case 0:
+ break;
+ case -1:
+ if (errno != EINTR)
+ krb5_warn(context, rk_SOCK_ERRNO, "select");
+ break;
+ default:
+#ifdef HAVE_FORK
+ if (islive > -1 && FD_ISSET(islive, &fds))
+ handle_islive(islive);
+#endif
+ for (i = 0; i < ndescr; i++)
+ if (!rk_IS_BAD_SOCKET(d[i].s) && FD_ISSET(d[i].s, &fds)) {
+ min_free = next_min_free(context, dp, ndescrp);
+ ndescr = *ndescrp;
+ d = *dp;
+
+ if (d[i].type == SOCK_DGRAM)
+ handle_udp(context, config, &d[i]);
+ else if (d[i].type == SOCK_STREAM)
+ handle_tcp(context, config, d, i, min_free);
+ }
+ }
+ }
+
+ switch (exit_flag) {
+ case -1:
+ kdc_log(context, config, 0,
+ "KDC worker process exiting because KDC master exited.");
+ break;
+#ifdef SIGXCPU
+ case SIGXCPU:
+ kdc_log(context, config, 0, "CPU time limit exceeded");
+ break;
+#endif
+ case SIGINT:
+ case SIGTERM:
+ kdc_log(context, config, 0, "Terminated");
+ break;
+ default:
+ kdc_log(context, config, 0, "Unexpected exit reason: %d", exit_flag);
+ break;
+ }
+}
+
+#ifdef __APPLE__
+static void
+bonjour_kid(krb5_context context, krb5_kdc_configuration *config, const char *argv0, int *islive)
+{
+ char buf;
+
+ if (do_bonjour > 0) {
+ bonjour_announce(context, config);
+
+ while (read(0, &buf, 1) == 1)
+ continue;
+ _exit(0);
+ }
+
+ if ((bonjour_pid = fork()) != 0)
+ return;
+
+ close(islive[0]);
+ if (dup2(islive[1], 0) == -1)
+ err(1, "failed to announce with bonjour (dup)");
+ if (islive[1] != 0)
+ close(islive[1]);
+ execlp(argv0, "kdc", "--bonjour", NULL);
+ err(1, "failed to announce with bonjour (exec)");
+}
+#endif
+
+#ifdef HAVE_FORK
+static void
+kill_kids(pid_t *pids, int max_kids, int sig)
+{
+ int i;
+
+ for (i=0; i < max_kids; i++)
+ if (pids[i] > 0)
+ kill(sig, pids[i]);
+ if (bonjour_pid > 0)
+ kill(sig, bonjour_pid);
+}
+
+static int
+reap_kid(krb5_context context, krb5_kdc_configuration *config,
+ pid_t *pids, int max_kids, int options)
+{
+ pid_t pid;
+ char *what = "untracked";
+ int status;
+ int i = 0; /* quiet warnings */
+ int ret = 0;
+ int level = 3;
+ const char *sev = "info: ";
+
+ pid = waitpid(-1, &status, options);
+ if (pid <= 0)
+ return 0;
+
+ if (pid == bonjour_pid) {
+ bonjour_pid = (pid_t)-1;
+ what = "bonjour";
+ } else {
+ for (i=0; i < max_kids; i++) {
+ if (pids[i] == pid) {
+ pids[i] = (pid_t)-1;
+ what = "worker";
+ ret = 1;
+ break;
+ }
+ }
+
+ if (i == max_kids) {
+ /* should not happen */
+ sev = "warning: ";
+ level = 2;
+ }
+ }
+
+ if (WIFEXITED(status))
+ kdc_log(context, config, level,
+ "%sKDC reaped %s process: %d, exit status: %d",
+ sev, what, (int)pid, WEXITSTATUS(status));
+ else if (WIFSIGNALED(status))
+ kdc_log(context, config, level,
+ "%sKDC reaped %s process: %d, term signal %d%s",
+ sev, what, (int)pid, WTERMSIG(status),
+ WCOREDUMP(status) ? " (core dumped)" : "");
+ else
+ kdc_log(context, config, level, "%sKDC reaped %s process: %d",
+ sev, what, (int)pid);
+
+ return ret;
+}
+
+static int
+reap_kids(krb5_context context, krb5_kdc_configuration *config,
+ pid_t *pids, int max_kids)
+{
+ int reaped = 0;
+
+ for (;;) {
+ if (reap_kid(context, config, pids, max_kids, WNOHANG) == 0)
+ break;
+ reaped++;
+ }
+
+ return reaped;
+}
+
+static void
+select_sleep(int microseconds)
+{
+ struct timeval tv;
+
+ tv.tv_sec = microseconds / 1000000;
+ tv.tv_usec = microseconds % 1000000;
+ select(0, NULL, NULL, NULL, &tv);
+}
+#endif
+
+void
+start_kdc(krb5_context context,
+ krb5_kdc_configuration *config, const char *argv0)
+{
+ struct timeval tv1;
+ struct timeval tv2;
+ struct descr *d;
+ unsigned int ndescr;
+ pid_t pid = -1;
+#ifdef HAVE_FORK
+ pid_t *pids;
+ int max_kdcs = config->num_kdc_processes;
+ int num_kdcs = 0;
+ int i;
+ int islive[2];
+#endif
+
+#ifdef __APPLE__
+ if (!testing_flag && do_bonjour > 0)
+ bonjour_kid(context, config, argv0, NULL);
+#endif
+
+#ifdef HAVE_FORK
+#ifdef _SC_NPROCESSORS_ONLN
+ if (max_kdcs < 1)
+ max_kdcs = sysconf(_SC_NPROCESSORS_ONLN);
+#endif
+
+ if (max_kdcs < 1)
+ max_kdcs = 1;
+
+ pids = calloc(max_kdcs, sizeof(*pids));
+ if (pids == NULL)
+ krb5_err(context, 1, errno, "malloc");
+
+ /*
+ * We open a socketpair of which we hand one end to each of our kids.
+ * When we exit, for whatever reason, the children will notice an EOF
+ * on their end and be able to cleanly exit.
+ */
+
+ if (socketpair(PF_UNIX, SOCK_STREAM, 0, islive) == -1)
+ krb5_errx(context, 1, "socketpair");
+ socket_set_nonblocking(islive[1], 1);
+#endif
+
+ ndescr = init_sockets(context, config, &d);
+ if(ndescr <= 0)
+ krb5_errx(context, 1, "No sockets!");
+
+#ifdef HAVE_FORK
+
+# ifdef __APPLE__
+ if (!testing_flag && do_bonjour < 0)
+ bonjour_kid(context, config, argv0, islive);
+# endif
+
+ kdc_log(context, config, 3, "KDC started master process pid=%d", getpid());
+#else
+ kdc_log(context, config, 3, "KDC started pid=%d", getpid());
+#endif
+
+ roken_detach_finish(NULL, daemon_child);
+
+#ifdef HAVE_FORK
+ if (!testing_flag) {
+ /* Note that we might never execute the body of this loop */
+ while (exit_flag == 0) {
+
+ if (num_kdcs >= max_kdcs) {
+ num_kdcs -= reap_kid(context, config, pids, max_kdcs, 0);
+ continue;
+ }
+
+ if (num_kdcs > 0)
+ num_kdcs -= reap_kids(context, config, pids, max_kdcs);
+
+ pid = fork();
+ switch (pid) {
+ case 0:
+ close(islive[0]);
+ loop(context, config, &d, &ndescr, islive[1]);
+ exit(0);
+ case -1:
+ /* XXXrcd: hmmm, do something useful?? */
+ kdc_log(context, config, 1,
+ "KDC master process could not fork worker process");
+ sleep(10);
+ break;
+ default:
+ for (i = 0; i < max_kdcs; i++) {
+ if (pids[i] <= 0) {
+ pids[i] = pid;
+ break;
+ }
+ }
+ if (i >= max_kdcs) {
+ /* This should not happen */
+ kdc_log(context, config, 1,
+ "warning: forked untracked child process: %d",
+ (int)pid);
+ }
+ kdc_log(context, config, 3, "KDC worker process started: %d",
+ pid);
+ num_kdcs++;
+ /* Slow down the creation of KDCs... */
+ select_sleep(12500);
+ break;
+ }
+ }
+
+ /* Closing these sockets should cause the kids to die... */
+
+ close(islive[0]);
+ close(islive[1]);
+
+ /* Close our listener sockets before terminating workers */
+ for (i = 0; i < ndescr; ++i)
+ clear_descr(&d[i]);
+
+ gettimeofday(&tv1, NULL);
+ tv2 = tv1;
+
+ /* Reap every 10ms, terminate stragglers once a second, give up after 10 */
+ for (;;) {
+ struct timeval tv3;
+ num_kdcs -= reap_kids(context, config, pids, max_kdcs);
+ if (num_kdcs == 0 && bonjour_pid <= 0)
+ goto end;
+ /*
+ * Using select to sleep will fail with EINTR if we receive a
+ * SIGCHLD. This is desirable.
+ */
+ select_sleep(10000);
+ gettimeofday(&tv3, NULL);
+ if (tv3.tv_sec - tv1.tv_sec > 10 ||
+ (tv3.tv_sec - tv1.tv_sec == 10 && tv3.tv_usec >= tv1.tv_usec))
+ break;
+ if (tv3.tv_sec - tv2.tv_sec > 1 ||
+ (tv3.tv_sec - tv2.tv_sec == 1 && tv3.tv_usec >= tv2.tv_usec)) {
+ kill_kids(pids, max_kdcs, SIGTERM);
+ tv2 = tv3;
+ }
+ }
+
+ /* Kill stragglers and reap every 200ms, give up after 15s */
+ for (;;) {
+ kill_kids(pids, max_kdcs, SIGKILL);
+ num_kdcs -= reap_kids(context, config, pids, max_kdcs);
+ if (num_kdcs == 0 && bonjour_pid <= 0)
+ break;
+ select_sleep(200000);
+ gettimeofday(&tv2, NULL);
+ if (tv2.tv_sec - tv1.tv_sec > 15 ||
+ (tv2.tv_sec - tv1.tv_sec == 15 && tv2.tv_usec >= tv1.tv_usec))
+ break;
+ }
+
+ end:
+ kdc_log(context, config, 3, "KDC master process exiting");
+ } else {
+ loop(context, config, &d, &ndescr, -1);
+ kdc_log(context, config, 3, "KDC exiting");
+ }
+ free(pids);
+#else
+ loop(context, config, &d, &ndescr, -1);
+ kdc_log(context, config, 3, "KDC exiting");
+#endif
+
+ free(d);
+}
diff --git a/third_party/heimdal/kdc/csr_authorizer.c b/third_party/heimdal/kdc/csr_authorizer.c
new file mode 100644
index 0000000..52bc37c
--- /dev/null
+++ b/third_party/heimdal/kdc/csr_authorizer.c
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2019 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "kdc_locl.h"
+#include "csr_authorizer_plugin.h"
+
+struct plctx {
+ const char *app;
+ hx509_request csr;
+ krb5_const_principal client;
+ krb5_boolean result;
+};
+
+static krb5_error_code KRB5_LIB_CALL
+plcallback(krb5_context context, const void *plug, void *plugctx, void *userctx)
+{
+ const krb5plugin_csr_authorizer_ftable *authorizer = plug;
+ struct plctx *plctx = userctx;
+
+ return authorizer->authorize(plugctx, context, plctx->app, plctx->csr,
+ plctx->client, &plctx->result);
+}
+
+static const char *plugin_deps[] = { "krb5", NULL };
+
+static struct heim_plugin_data csr_authorizer_data = {
+ "kdc",
+ KDC_CSR_AUTHORIZER,
+ 1,
+ plugin_deps,
+ krb5_get_instance
+};
+
+/*
+ * Invoke a plugin to validate a JWT/SAML/OIDC token and partially-evaluate
+ * access control.
+ */
+KDC_LIB_FUNCTION krb5_error_code KDC_LIB_CALL
+kdc_authorize_csr(krb5_context context,
+ const char *app,
+ hx509_request csr,
+ krb5_const_principal client)
+{
+ krb5_error_code ret;
+ struct plctx ctx;
+
+ ctx.app = app;
+ ctx.csr = csr;
+ ctx.client = client;
+ ctx.result = FALSE;
+
+ ret = _krb5_plugin_run_f(context, &csr_authorizer_data, 0, &ctx,
+ plcallback);
+ if (ret)
+ krb5_prepend_error_message(context, ret, "Authorization of requested "
+ "certificate extensions failed");
+ else if (!ctx.result)
+ krb5_set_error_message(context, ret, "Authorization of requested "
+ "certificate extensions failed");
+ return ret;
+}
diff --git a/third_party/heimdal/kdc/csr_authorizer_plugin.h b/third_party/heimdal/kdc/csr_authorizer_plugin.h
new file mode 100644
index 0000000..022feda
--- /dev/null
+++ b/third_party/heimdal/kdc/csr_authorizer_plugin.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2019 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef HEIMDAL_KDC_CSR_AUTHORIZER_PLUGIN_H
+#define HEIMDAL_KDC_CSR_AUTHORIZER_PLUGIN_H 1
+
+#define KDC_CSR_AUTHORIZER "kdc_csr_authorizer"
+#define KDC_CSR_AUTHORIZER_VERSION_0 0
+
+/*
+ * @param init Plugin initialization function (see krb5-plugin(7))
+ * @param minor_version The plugin minor version number (0)
+ * @param fini Plugin finalization function
+ * @param authorize Plugin CSR authorization function
+ *
+ * The authorize field is the plugin entry point that performs authorization of
+ * CSRs for kx509 however the plugin desires. It is invoked in no particular
+ * order relative to other CSR authorization plugins. The plugin authorize
+ * function must return KRB5_PLUGIN_NO_HANDLE if the rule is not applicable to
+ * it.
+ *
+ * The plugin authorize function has the following arguments, in this
+ * order:
+ *
+ * -# plug_ctx, the context value output by the plugin's init function
+ * -# context, a krb5_context
+ * -# app, the name of the application
+ * -# csr, a hx509_request
+ * -# client, a krb5_const_principal
+ * -# authorization_result, a pointer to a krb5_boolean
+ *
+ * @ingroup krb5_support
+ */
+typedef struct krb5plugin_csr_authorizer_ftable_desc {
+ HEIM_PLUGIN_FTABLE_COMMON_ELEMENTS(krb5_context);
+ krb5_error_code (KRB5_LIB_CALL *authorize)(void *, /*plug_ctx*/
+ krb5_context, /*context*/
+ const char *, /*app*/
+ hx509_request, /*CSR*/
+ krb5_const_principal,/*client*/
+ krb5_boolean *); /*authorized*/
+} krb5plugin_csr_authorizer_ftable;
+
+#endif /* HEIMDAL_KDC_CSR_AUTHORIZER_PLUGIN_H */
diff --git a/third_party/heimdal/kdc/default_config.c b/third_party/heimdal/kdc/default_config.c
new file mode 100644
index 0000000..8301b90
--- /dev/null
+++ b/third_party/heimdal/kdc/default_config.c
@@ -0,0 +1,464 @@
+/*
+ * Copyright (c) 1997-2007 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "kdc_locl.h"
+#include <getarg.h>
+#include <parse_bytes.h>
+
+static const char *sysplugin_dirs[] = {
+#ifdef _WIN32
+ "$ORIGIN",
+#else
+ "$ORIGIN/../lib/plugin/kdc",
+#endif
+#ifdef __APPLE__
+ LIBDIR "/plugin/kdc",
+#endif
+ NULL
+};
+
+static void
+load_kdc_plugins_once(void *ctx)
+{
+ krb5_context context = ctx;
+ const char * const *dirs = sysplugin_dirs;
+#ifndef _WIN32
+ char **cfdirs;
+
+ cfdirs = krb5_config_get_strings(context, NULL, "kdc", "plugin_dir", NULL);
+ if (cfdirs)
+ dirs = (const char * const *)cfdirs;
+#endif
+
+ _krb5_load_plugins(context, "kdc", (const char **)dirs);
+
+#ifndef _WIN32
+ krb5_config_free_strings(cfdirs);
+#endif
+}
+
+KDC_LIB_FUNCTION krb5_error_code KDC_LIB_CALL
+krb5_kdc_get_config(krb5_context context, krb5_kdc_configuration **config)
+{
+ static heim_base_once_t load_kdc_plugins = HEIM_BASE_ONCE_INIT;
+ krb5_kdc_configuration *c;
+ krb5_error_code ret;
+
+ heim_base_once_f(&load_kdc_plugins, context, load_kdc_plugins_once);
+
+ c = calloc(1, sizeof(*c));
+ if (c == NULL) {
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+
+ c->app = "kdc";
+ c->num_kdc_processes = -1;
+ c->require_preauth = TRUE;
+ c->kdc_warn_pwexpire = 0;
+ c->encode_as_rep_as_tgs_rep = FALSE;
+ c->tgt_use_strongest_session_key = FALSE;
+ c->preauth_use_strongest_session_key = FALSE;
+ c->svc_use_strongest_session_key = FALSE;
+ c->use_strongest_server_key = TRUE;
+ c->check_ticket_addresses = TRUE;
+ c->warn_ticket_addresses = FALSE;
+ c->allow_null_ticket_addresses = TRUE;
+ c->allow_anonymous = FALSE;
+ c->historical_anon_realm = FALSE;
+ c->strict_nametypes = FALSE;
+ c->trpolicy = TRPOLICY_ALWAYS_CHECK;
+ c->require_pac = FALSE;
+ c->disable_pac = FALSE;
+ c->enable_fast = TRUE;
+ c->enable_fast_cookie = TRUE;
+ c->enable_armored_pa_enc_timestamp = TRUE;
+ c->enable_unarmored_pa_enc_timestamp = TRUE;
+ c->enable_pkinit = FALSE;
+ c->require_pkinit_freshness = FALSE;
+ c->pkinit_princ_in_cert = TRUE;
+ c->pkinit_require_binding = TRUE;
+ c->synthetic_clients = FALSE;
+ c->pkinit_max_life_from_cert_extension = FALSE;
+ c->pkinit_max_life_bound = 0;
+ c->synthetic_clients_max_life = 300;
+ c->synthetic_clients_max_renew = 300;
+ c->pkinit_dh_min_bits = 1024;
+ c->db = NULL;
+ c->num_db = 0;
+ c->logf = NULL;
+
+ c->num_kdc_processes =
+ krb5_config_get_int_default(context, NULL, c->num_kdc_processes,
+ "kdc", "num-kdc-processes", NULL);
+
+ c->require_preauth =
+ krb5_config_get_bool_default(context, NULL,
+ c->require_preauth,
+ "kdc", "require-preauth", NULL);
+#ifdef DIGEST
+ c->enable_digest =
+ krb5_config_get_bool_default(context, NULL,
+ FALSE,
+ "kdc", "enable-digest", NULL);
+
+ {
+ const char *digests;
+
+ digests = krb5_config_get_string(context, NULL,
+ "kdc",
+ "digests_allowed", NULL);
+ if (digests == NULL)
+ digests = "ntlm-v2";
+ c->digests_allowed = parse_flags(digests,_kdc_digestunits, 0);
+ if (c->digests_allowed == -1) {
+ kdc_log(context, c, 0,
+ "unparsable digest units (%s), turning off digest",
+ digests);
+ c->enable_digest = 0;
+ } else if (c->digests_allowed == 0) {
+ kdc_log(context, c, 0, "no digest enable, turning digest off");
+ c->enable_digest = 0;
+ }
+ }
+#endif
+
+#ifdef KX509
+ c->enable_kx509 =
+ krb5_config_get_bool_default(context, NULL,
+ FALSE,
+ "kdc", "enable_kx509", NULL);
+#endif
+
+ c->tgt_use_strongest_session_key =
+ krb5_config_get_bool_default(context, NULL,
+ c->tgt_use_strongest_session_key,
+ "kdc",
+ "tgt-use-strongest-session-key", NULL);
+ c->preauth_use_strongest_session_key =
+ krb5_config_get_bool_default(context, NULL,
+ c->preauth_use_strongest_session_key,
+ "kdc",
+ "preauth-use-strongest-session-key", NULL);
+ c->svc_use_strongest_session_key =
+ krb5_config_get_bool_default(context, NULL,
+ c->svc_use_strongest_session_key,
+ "kdc",
+ "svc-use-strongest-session-key", NULL);
+ c->use_strongest_server_key =
+ krb5_config_get_bool_default(context, NULL,
+ c->use_strongest_server_key,
+ "kdc",
+ "use-strongest-server-key", NULL);
+
+ c->check_ticket_addresses =
+ krb5_config_get_bool_default(context, NULL,
+ c->check_ticket_addresses,
+ "kdc",
+ "check-ticket-addresses", NULL);
+ c->warn_ticket_addresses =
+ krb5_config_get_bool_default(context, NULL,
+ c->warn_ticket_addresses,
+ "kdc",
+ "warn_ticket_addresses", NULL);
+ c->allow_null_ticket_addresses =
+ krb5_config_get_bool_default(context, NULL,
+ c->allow_null_ticket_addresses,
+ "kdc",
+ "allow-null-ticket-addresses", NULL);
+
+ c->allow_anonymous =
+ krb5_config_get_bool_default(context, NULL,
+ c->allow_anonymous,
+ "kdc",
+ "allow-anonymous", NULL);
+
+ c->historical_anon_realm =
+ krb5_config_get_bool_default(context, NULL,
+ c->historical_anon_realm,
+ "kdc",
+ "historical_anon_realm", NULL);
+
+ c->strict_nametypes =
+ krb5_config_get_bool_default(context, NULL,
+ c->strict_nametypes,
+ "kdc",
+ "strict-nametypes", NULL);
+
+ c->max_datagram_reply_length =
+ krb5_config_get_int_default(context,
+ NULL,
+ 1400,
+ "kdc",
+ "max-kdc-datagram-reply-length",
+ NULL);
+
+ {
+ const char *trpolicy_str;
+
+ trpolicy_str =
+ krb5_config_get_string_default(context, NULL, "DEFAULT", "kdc",
+ "transited-policy", NULL);
+ if(strcasecmp(trpolicy_str, "always-check") == 0) {
+ c->trpolicy = TRPOLICY_ALWAYS_CHECK;
+ } else if(strcasecmp(trpolicy_str, "allow-per-principal") == 0) {
+ c->trpolicy = TRPOLICY_ALLOW_PER_PRINCIPAL;
+ } else if(strcasecmp(trpolicy_str, "always-honour-request") == 0) {
+ c->trpolicy = TRPOLICY_ALWAYS_HONOUR_REQUEST;
+ } else if(strcasecmp(trpolicy_str, "DEFAULT") == 0) {
+ /* default */
+ } else {
+ kdc_log(context, c, 0,
+ "unknown transited-policy: %s, "
+ "reverting to default (always-check)",
+ trpolicy_str);
+ }
+ }
+
+ c->encode_as_rep_as_tgs_rep =
+ krb5_config_get_bool_default(context, NULL,
+ c->encode_as_rep_as_tgs_rep,
+ "kdc",
+ "encode_as_rep_as_tgs_rep", NULL);
+
+ c->kdc_warn_pwexpire =
+ krb5_config_get_time_default (context, NULL,
+ c->kdc_warn_pwexpire,
+ "kdc", "kdc_warn_pwexpire", NULL);
+
+ c->require_pac =
+ krb5_config_get_bool_default(context,
+ NULL,
+ c->require_pac,
+ "kdc",
+ "require_pac",
+ NULL);
+
+ c->disable_pac =
+ krb5_config_get_bool_default(context,
+ NULL,
+ c->disable_pac,
+ "kdc",
+ "disable_pac",
+ NULL);
+
+ c->enable_fast =
+ krb5_config_get_bool_default(context,
+ NULL,
+ c->enable_fast,
+ "kdc",
+ "enable_fast",
+ NULL);
+
+ c->enable_fast_cookie =
+ krb5_config_get_bool_default(context,
+ NULL,
+ c->enable_fast_cookie,
+ "kdc",
+ "enable_fast_cookie",
+ NULL);
+
+ c->enable_armored_pa_enc_timestamp =
+ krb5_config_get_bool_default(context,
+ NULL,
+ c->enable_armored_pa_enc_timestamp,
+ "kdc",
+ "enable_armored_pa_enc_timestamp",
+ NULL);
+
+ c->enable_unarmored_pa_enc_timestamp =
+ krb5_config_get_bool_default(context,
+ NULL,
+ c->enable_unarmored_pa_enc_timestamp,
+ "kdc",
+ "enable_unarmored_pa_enc_timestamp",
+ NULL);
+
+ c->enable_pkinit =
+ krb5_config_get_bool_default(context,
+ NULL,
+ c->enable_pkinit,
+ "kdc",
+ "enable-pkinit",
+ NULL);
+
+ c->require_pkinit_freshness =
+ krb5_config_get_bool_default(context,
+ NULL,
+ c->require_pkinit_freshness,
+ "kdc",
+ "require-pkinit-freshness",
+ NULL);
+
+ c->pkinit_kdc_identity =
+ krb5_config_get_string(context, NULL,
+ "kdc", "pkinit_identity", NULL);
+ c->pkinit_kdc_anchors =
+ krb5_config_get_string(context, NULL,
+ "kdc", "pkinit_anchors", NULL);
+ c->pkinit_kdc_cert_pool =
+ krb5_config_get_strings(context, NULL,
+ "kdc", "pkinit_pool", NULL);
+ c->pkinit_kdc_revoke =
+ krb5_config_get_strings(context, NULL,
+ "kdc", "pkinit_revoke", NULL);
+ c->pkinit_kdc_ocsp_file =
+ krb5_config_get_string(context, NULL,
+ "kdc", "pkinit_kdc_ocsp", NULL);
+ c->pkinit_kdc_friendly_name =
+ krb5_config_get_string(context, NULL,
+ "kdc", "pkinit_kdc_friendly_name", NULL);
+ c->pkinit_princ_in_cert =
+ krb5_config_get_bool_default(context, NULL,
+ c->pkinit_princ_in_cert,
+ "kdc",
+ "pkinit_principal_in_certificate",
+ NULL);
+ c->pkinit_require_binding =
+ krb5_config_get_bool_default(context, NULL,
+ c->pkinit_require_binding,
+ "kdc",
+ "pkinit_win2k_require_binding",
+ NULL);
+ c->pkinit_dh_min_bits =
+ krb5_config_get_int_default(context, NULL,
+ 0,
+ "kdc", "pkinit_dh_min_bits", NULL);
+
+ c->pkinit_max_life_from_cert_extension =
+ krb5_config_get_bool_default(context, NULL,
+ c->pkinit_max_life_from_cert_extension,
+ "kdc",
+ "pkinit_max_life_from_cert_extension",
+ NULL);
+
+ c->synthetic_clients =
+ krb5_config_get_bool_default(context, NULL,
+ c->synthetic_clients,
+ "kdc",
+ "synthetic_clients",
+ NULL);
+
+ c->pkinit_max_life_bound =
+ krb5_config_get_time_default(context, NULL, 0, "kdc",
+ "pkinit_max_life_bound",
+ NULL);
+
+ c->pkinit_max_life_from_cert =
+ krb5_config_get_time_default(context, NULL, 0, "kdc",
+ "pkinit_max_life_from_cert",
+ NULL);
+
+ c->synthetic_clients_max_life =
+ krb5_config_get_time_default(context, NULL, 300, "kdc",
+ "synthetic_clients_max_life",
+ NULL);
+
+ c->synthetic_clients_max_renew =
+ krb5_config_get_time_default(context, NULL, 300, "kdc",
+ "synthetic_clients_max_renew",
+ NULL);
+
+ c->enable_gss_preauth =
+ krb5_config_get_bool_default(context, NULL,
+ c->enable_gss_preauth,
+ "kdc",
+ "enable_gss_preauth", NULL);
+
+ c->enable_gss_auth_data =
+ krb5_config_get_bool_default(context, NULL,
+ c->enable_gss_auth_data,
+ "kdc",
+ "enable_gss_auth_data", NULL);
+
+ ret = _kdc_gss_get_mechanism_config(context, "kdc",
+ "gss_mechanisms_allowed",
+ &c->gss_mechanisms_allowed);
+ if (ret) {
+ free(c);
+ return ret;
+ }
+
+ ret = _kdc_gss_get_mechanism_config(context, "kdc",
+ "gss_cross_realm_mechanisms_allowed",
+ &c->gss_cross_realm_mechanisms_allowed);
+ if (ret) {
+ OM_uint32 minor;
+ gss_release_oid_set(&minor, &c->gss_mechanisms_allowed);
+ free(c);
+ return ret;
+ }
+
+ *config = c;
+
+ return 0;
+}
+
+KDC_LIB_FUNCTION krb5_error_code KDC_LIB_CALL
+krb5_kdc_pkinit_config(krb5_context context, krb5_kdc_configuration *config)
+{
+#ifdef PKINIT
+#ifdef __APPLE__
+ config->enable_pkinit = 1;
+
+ if (config->pkinit_kdc_identity == NULL) {
+ if (config->pkinit_kdc_friendly_name == NULL)
+ config->pkinit_kdc_friendly_name =
+ strdup("O=System Identity,CN=com.apple.kerberos.kdc");
+ config->pkinit_kdc_identity = strdup("KEYCHAIN:");
+ }
+ if (config->pkinit_kdc_anchors == NULL)
+ config->pkinit_kdc_anchors = strdup("KEYCHAIN:");
+
+#endif /* __APPLE__ */
+
+ if (config->enable_pkinit) {
+ if (config->pkinit_kdc_identity == NULL)
+ krb5_errx(context, 1, "pkinit enabled but no identity");
+
+ if (config->pkinit_kdc_anchors == NULL)
+ krb5_errx(context, 1, "pkinit enabled but no X509 anchors");
+
+ krb5_kdc_pk_initialize(context, config,
+ config->pkinit_kdc_identity,
+ config->pkinit_kdc_anchors,
+ config->pkinit_kdc_cert_pool,
+ config->pkinit_kdc_revoke);
+
+ }
+
+ return 0;
+#endif /* PKINIT */
+}
diff --git a/third_party/heimdal/kdc/digest-service.c b/third_party/heimdal/kdc/digest-service.c
new file mode 100644
index 0000000..275efaf
--- /dev/null
+++ b/third_party/heimdal/kdc/digest-service.c
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2006 - 2007 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#define HC_DEPRECATED_CRYPTO
+
+#include "headers.h"
+#include <digest_asn1.h>
+#include <heimntlm.h>
+#include <heim-ipc.h>
+#include <getarg.h>
+
+typedef struct pk_client_params pk_client_params;
+typedef struct gss_client_params gss_client_params;
+struct DigestREQ;
+struct Kx509Request;
+
+#include <kdc-private.h>
+
+krb5_kdc_configuration *config;
+
+static void
+ntlm_service(void *ctx, const heim_idata *req,
+ const heim_icred cred,
+ heim_ipc_complete complete,
+ heim_sipc_call cctx)
+{
+ NTLMRequest2 ntq;
+ unsigned char sessionkey[16];
+ heim_idata rep = { 0, NULL };
+ krb5_context context = ctx;
+ hdb_entry *user = NULL;
+ HDB *db = NULL;
+ Key *key = NULL;
+ NTLMReply ntp;
+ size_t size;
+ int ret;
+ const char *domain;
+
+ kdc_log(context, config, 4, "digest-request: uid=%d",
+ (int)heim_ipc_cred_get_uid(cred));
+
+ if (heim_ipc_cred_get_uid(cred) != 0) {
+ (*complete)(cctx, EPERM, NULL);
+ return;
+ }
+
+ ntp.success = 0;
+ ntp.flags = 0;
+ ntp.sessionkey = NULL;
+
+ ret = decode_NTLMRequest2(req->data, req->length, &ntq, NULL);
+ if (ret)
+ goto failed;
+
+ /* XXX forward to NetrLogonSamLogonEx() if not a local domain */
+ if (strcmp(ntq.loginDomainName, "BUILTIN") == 0) {
+ domain = ntq.loginDomainName;
+ } else if (strcmp(ntq.loginDomainName, "") == 0) {
+ domain = "BUILTIN";
+ } else {
+ ret = EINVAL;
+ goto failed;
+ }
+
+ kdc_log(context, config, 4, "digest-request: user=%s/%s",
+ ntq.loginUserName, domain);
+
+ if (ntq.lmchallenge.length != 8)
+ goto failed;
+
+ if (ntq.ntChallengeResponce.length == 0)
+ goto failed;
+
+ {
+ krb5_principal client;
+
+ ret = krb5_make_principal(context, &client, domain,
+ ntq.loginUserName, NULL);
+ if (ret)
+ goto failed;
+
+ krb5_principal_set_type(context, client, KRB5_NT_NTLM);
+
+ ret = _kdc_db_fetch(context, config, client,
+ HDB_F_GET_CLIENT, NULL, &db, &user);
+ krb5_free_principal(context, client);
+ if (ret)
+ goto failed;
+
+ ret = hdb_enctype2key(context, user, NULL,
+ ETYPE_ARCFOUR_HMAC_MD5, &key);
+ if (ret) {
+ krb5_set_error_message(context, ret, "NTLM missing arcfour key");
+ goto failed;
+ }
+ }
+
+ kdc_log(context, config, 5,
+ "digest-request: found user, processing ntlm request");
+
+ if (ntq.ntChallengeResponce.length != 24) {
+ struct ntlm_buf infotarget, answer;
+
+ answer.length = ntq.ntChallengeResponce.length;
+ answer.data = ntq.ntChallengeResponce.data;
+
+ ret = heim_ntlm_verify_ntlm2(key->key.keyvalue.data,
+ key->key.keyvalue.length,
+ ntq.loginUserName,
+ ntq.loginDomainName,
+ 0,
+ ntq.lmchallenge.data,
+ &answer,
+ &infotarget,
+ sessionkey);
+ if (ret) {
+ goto failed;
+ }
+
+ free(infotarget.data);
+ /* XXX verify info target */
+
+ } else {
+ struct ntlm_buf answer;
+
+ if (ntq.flags & NTLM_NEG_NTLM2_SESSION) {
+ unsigned char sessionhash[MD5_DIGEST_LENGTH];
+ EVP_MD_CTX *md5ctx;
+
+ /* the first first 8 bytes is the challenge, what is the other 16 bytes ? */
+ if (ntq.lmChallengeResponce.length != 24)
+ goto failed;
+
+ md5ctx = EVP_MD_CTX_create();
+ EVP_DigestInit_ex(md5ctx, EVP_md5(), NULL);
+ EVP_DigestUpdate(md5ctx, ntq.lmchallenge.data, 8);
+ EVP_DigestUpdate(md5ctx, ntq.lmChallengeResponce.data, 8);
+ EVP_DigestFinal_ex(md5ctx, sessionhash, NULL);
+ EVP_MD_CTX_destroy(md5ctx);
+ memcpy(ntq.lmchallenge.data, sessionhash, ntq.lmchallenge.length);
+ }
+
+ ret = heim_ntlm_calculate_ntlm1(key->key.keyvalue.data,
+ key->key.keyvalue.length,
+ ntq.lmchallenge.data, &answer);
+ if (ret)
+ goto failed;
+
+ if (ntq.ntChallengeResponce.length != answer.length ||
+ ct_memcmp(ntq.ntChallengeResponce.data, answer.data, answer.length) != 0) {
+ free(answer.data);
+ ret = EINVAL;
+ goto failed;
+ }
+ free(answer.data);
+
+ {
+ EVP_MD_CTX *ctxp;
+
+ ctxp = EVP_MD_CTX_create();
+ EVP_DigestInit_ex(ctxp, EVP_md4(), NULL);
+ EVP_DigestUpdate(ctxp, key->key.keyvalue.data, key->key.keyvalue.length);
+ EVP_DigestFinal_ex(ctxp, sessionkey, NULL);
+ EVP_MD_CTX_destroy(ctxp);
+ }
+ }
+
+ ntp.success = 1;
+
+ ASN1_MALLOC_ENCODE(NTLMReply, rep.data, rep.length, &ntp, &size, ret);
+ if (ret)
+ goto failed;
+ if (rep.length != size)
+ abort();
+
+ failed:
+ kdc_log(context, config, 4, "digest-request: %d", ret);
+
+ (*complete)(cctx, ret, &rep);
+
+ free(rep.data);
+
+ free_NTLMRequest2(&ntq);
+ if (user)
+ _kdc_free_ent (context, db, user);
+}
+
+static int help_flag;
+static int version_flag;
+
+static struct getargs args[] = {
+ { "help", 'h', arg_flag, &help_flag, NULL, NULL },
+ { "version", 'v', arg_flag, &version_flag, NULL, NULL }
+};
+
+static int num_args = sizeof(args) / sizeof(args[0]);
+
+static void
+usage(int ret)
+{
+ arg_printusage (args, num_args, NULL, "");
+ exit (ret);
+}
+
+int
+main(int argc, char **argv)
+{
+ krb5_context context;
+ int ret, optidx = 0;
+
+ setprogname(argv[0]);
+
+ if (getarg(args, num_args, argc, argv, &optidx))
+ usage(1);
+
+ if (help_flag)
+ usage(0);
+
+ if (version_flag) {
+ print_version(NULL);
+ exit(0);
+ }
+
+ ret = krb5_init_context(&context);
+ if (ret)
+ krb5_errx(context, 1, "krb5_init_context");
+
+ ret = krb5_kdc_get_config(context, &config);
+ if (ret)
+ krb5_err(context, 1, ret, "krb5_kdc_default_config");
+
+ kdc_openlog(context, "digest-service", config);
+
+ ret = krb5_kdc_set_dbinfo(context, config);
+ if (ret)
+ krb5_err(context, 1, ret, "krb5_kdc_set_dbinfo");
+
+#if __APPLE__
+ {
+ heim_sipc mach;
+ heim_sipc_launchd_mach_init("org.h5l.ntlm-service",
+ ntlm_service, context, &mach);
+ heim_sipc_timeout(60);
+ }
+#endif
+#ifdef HAVE_DOOR_CREATE
+ {
+ heim_sipc door;
+ heim_sipc_service_door("org.h5l.ntlm-service", ntlm_service, NULL, &door);
+ }
+#endif
+ {
+ heim_sipc un;
+ heim_sipc_service_unix("org.h5l.ntlm-service", ntlm_service, NULL, &un);
+ }
+
+ heim_ipc_main();
+ return 0;
+}
diff --git a/third_party/heimdal/kdc/digest.c b/third_party/heimdal/kdc/digest.c
new file mode 100644
index 0000000..3285aaa
--- /dev/null
+++ b/third_party/heimdal/kdc/digest.c
@@ -0,0 +1,1520 @@
+/*
+ * Copyright (c) 2006 - 2007 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "kdc_locl.h"
+#include <hex.h>
+
+#ifdef DIGEST
+
+#define MS_CHAP_V2 0x20
+#define CHAP_MD5 0x10
+#define DIGEST_MD5 0x08
+#define NTLM_V2 0x04
+#define NTLM_V1_SESSION 0x02
+#define NTLM_V1 0x01
+
+const struct units _kdc_digestunits[] = {
+ {"ms-chap-v2", 1U << 5},
+ {"chap-md5", 1U << 4},
+ {"digest-md5", 1U << 3},
+ {"ntlm-v2", 1U << 2},
+ {"ntlm-v1-session", 1U << 1},
+ {"ntlm-v1", 1U << 0},
+ {NULL, 0}
+};
+
+
+static krb5_error_code
+get_digest_key(krb5_context context,
+ krb5_kdc_configuration *config,
+ hdb_entry *server,
+ krb5_crypto *crypto)
+{
+ krb5_error_code ret;
+ krb5_enctype enctype;
+ Key *key;
+
+ ret = _kdc_get_preferred_key(context,
+ config,
+ server,
+ "digest-service",
+ &enctype,
+ &key);
+ if (ret)
+ return ret;
+ return krb5_crypto_init(context, &key->key, 0, crypto);
+}
+
+/*
+ *
+ */
+
+static char *
+get_ntlm_targetname(krb5_context context,
+ hdb_entry *client)
+{
+ char *targetname, *p;
+
+ targetname = strdup(krb5_principal_get_realm(context,
+ client->principal));
+ if (targetname == NULL)
+ return NULL;
+
+ p = strchr(targetname, '.');
+ if (p)
+ *p = '\0';
+
+ strupr(targetname);
+ return targetname;
+}
+
+static krb5_error_code
+fill_targetinfo(krb5_context context,
+ char *targetname,
+ hdb_entry *client,
+ krb5_data *data)
+{
+ struct ntlm_targetinfo ti;
+ krb5_error_code ret;
+ struct ntlm_buf d;
+ krb5_principal p;
+ const char *str;
+
+ memset(&ti, 0, sizeof(ti));
+
+ ti.domainname = targetname;
+ p = client->principal;
+ str = krb5_principal_get_comp_string(context, p, 0);
+ if (str != NULL &&
+ (strcmp("host", str) == 0 ||
+ strcmp("ftp", str) == 0 ||
+ strcmp("imap", str) == 0 ||
+ strcmp("pop", str) == 0 ||
+ strcmp("smtp", str)))
+ {
+ str = krb5_principal_get_comp_string(context, p, 1);
+ ti.dnsservername = rk_UNCONST(str);
+ }
+
+ ret = heim_ntlm_encode_targetinfo(&ti, 1, &d);
+ if (ret)
+ return ret;
+
+ data->data = d.data;
+ data->length = d.length;
+
+ return 0;
+}
+
+
+static const unsigned char ms_chap_v2_magic1[39] = {
+ 0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76,
+ 0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65,
+ 0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67,
+ 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74
+};
+static const unsigned char ms_chap_v2_magic2[41] = {
+ 0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B,
+ 0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F,
+ 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E,
+ 0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F,
+ 0x6E
+};
+static const unsigned char ms_rfc3079_magic1[27] = {
+ 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
+ 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
+ 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79
+};
+
+/*
+ *
+ */
+
+static krb5_error_code
+get_password_entry(krb5_context context,
+ krb5_kdc_configuration *config,
+ const char *username,
+ char **password)
+{
+ krb5_principal clientprincipal;
+ krb5_error_code ret;
+ hdb_entry *user;
+ HDB *db;
+
+ /* get username */
+ ret = krb5_parse_name(context, username, &clientprincipal);
+ if (ret)
+ return ret;
+
+ ret = _kdc_db_fetch(context, config, clientprincipal,
+ HDB_F_GET_CLIENT, NULL, &db, &user);
+ krb5_free_principal(context, clientprincipal);
+ if (ret)
+ return ret;
+
+ ret = hdb_entry_get_password(context, db, user, password);
+ if (ret || password == NULL) {
+ if (ret == 0) {
+ ret = EINVAL;
+ krb5_set_error_message(context, ret, "password missing");
+ }
+ memset(user, 0, sizeof(*user));
+ }
+ _kdc_free_ent (context, db, user);
+ return ret;
+}
+
+/*
+ *
+ */
+
+krb5_error_code
+_kdc_do_digest(krb5_context context,
+ krb5_kdc_configuration *config,
+ const struct DigestREQ *req, krb5_data *reply,
+ const char *from, struct sockaddr *addr)
+{
+ krb5_error_code ret = 0;
+ krb5_ticket *ticket = NULL;
+ krb5_auth_context ac = NULL;
+ krb5_keytab id = NULL;
+ krb5_crypto crypto = NULL;
+ DigestReqInner ireq;
+ DigestRepInner r;
+ DigestREP rep;
+ krb5_flags ap_req_options;
+ krb5_data buf;
+ size_t size;
+ krb5_storage *sp = NULL;
+ Checksum res;
+ HDB *serverdb, *userdb;
+ hdb_entry *server = NULL, *user = NULL;
+ HDB *clientdb;
+ hdb_entry *client = NULL;
+ char *client_name = NULL, *password = NULL;
+ krb5_data serverNonce;
+
+ if(!config->enable_digest) {
+ kdc_log(context, config, 2,
+ "Rejected digest request (disabled) from %s", from);
+ return KRB5KDC_ERR_POLICY;
+ }
+
+ krb5_data_zero(&buf);
+ krb5_data_zero(reply);
+ krb5_data_zero(&serverNonce);
+ memset(&ireq, 0, sizeof(ireq));
+ memset(&r, 0, sizeof(r));
+ memset(&rep, 0, sizeof(rep));
+ memset(&res, 0, sizeof(res));
+
+ kdc_log(context, config, 3, "Digest request from %s", from);
+
+ ret = krb5_kt_resolve(context, "HDBGET:", &id);
+ if (ret) {
+ kdc_log(context, config, 0, "Can't open database for digest");
+ goto out;
+ }
+
+ ret = krb5_rd_req(context,
+ &ac,
+ &req->apReq,
+ NULL,
+ id,
+ &ap_req_options,
+ &ticket);
+ if (ret)
+ goto out;
+
+ /* check the server principal in the ticket matches digest/R@R */
+ {
+ krb5_principal principal = NULL;
+ const char *p, *rr;
+
+ ret = krb5_ticket_get_server(context, ticket, &principal);
+ if (ret)
+ goto out;
+
+ ret = EINVAL;
+ krb5_set_error_message(context, ret, "Wrong digest server principal used");
+ p = krb5_principal_get_comp_string(context, principal, 0);
+ if (p == NULL) {
+ krb5_free_principal(context, principal);
+ goto out;
+ }
+ if (strcmp(p, KRB5_DIGEST_NAME) != 0) {
+ krb5_free_principal(context, principal);
+ goto out;
+ }
+
+ p = krb5_principal_get_comp_string(context, principal, 1);
+ if (p == NULL) {
+ krb5_free_principal(context, principal);
+ goto out;
+ }
+ rr = krb5_principal_get_realm(context, principal);
+ if (rr == NULL) {
+ krb5_free_principal(context, principal);
+ goto out;
+ }
+ if (strcmp(p, rr) != 0) {
+ krb5_free_principal(context, principal);
+ goto out;
+ }
+ krb5_clear_error_message(context);
+
+ ret = _kdc_db_fetch(context, config, principal,
+ HDB_F_GET_SERVER, NULL, &serverdb, &server);
+ if (ret)
+ goto out;
+
+ krb5_free_principal(context, principal);
+ }
+
+ /* check the client is allowed to do digest auth */
+ {
+ krb5_principal principal = NULL;
+
+ ret = krb5_ticket_get_client(context, ticket, &principal);
+ if (ret)
+ goto out;
+
+ ret = krb5_unparse_name(context, principal, &client_name);
+ if (ret) {
+ krb5_free_principal(context, principal);
+ goto out;
+ }
+
+ ret = _kdc_db_fetch(context, config, principal,
+ HDB_F_GET_CLIENT, NULL, &clientdb, &client);
+ krb5_free_principal(context, principal);
+ if (ret)
+ goto out;
+
+ if (client->flags.allow_digest == 0) {
+ kdc_log(context, config, 2,
+ "Client %s tried to use digest "
+ "but is not allowed to",
+ client_name);
+ ret = KRB5KDC_ERR_POLICY;
+ krb5_set_error_message(context, ret,
+ "Client is not permitted to use digest");
+ goto out;
+ }
+ }
+
+ /* unpack request */
+ {
+ krb5_keyblock *key;
+
+ ret = krb5_auth_con_getremotesubkey(context, ac, &key);
+ if (ret)
+ goto out;
+ if (key == NULL) {
+ ret = EINVAL;
+ krb5_set_error_message(context, ret, "digest: remote subkey not found");
+ goto out;
+ }
+
+ ret = krb5_crypto_init(context, key, 0, &crypto);
+ krb5_free_keyblock (context, key);
+ if (ret)
+ goto out;
+ }
+
+ ret = krb5_decrypt_EncryptedData(context, crypto, KRB5_KU_DIGEST_ENCRYPT,
+ &req->innerReq, &buf);
+ krb5_crypto_destroy(context, crypto);
+ crypto = NULL;
+ if (ret)
+ goto out;
+
+ ret = decode_DigestReqInner(buf.data, buf.length, &ireq, NULL);
+ krb5_data_free(&buf);
+ if (ret) {
+ krb5_set_error_message(context, ret, "Failed to decode digest inner request");
+ goto out;
+ }
+
+ kdc_log(context, config, 3, "Valid digest request from %s (%s)",
+ client_name, from);
+
+ /*
+ * Process the inner request
+ */
+
+ switch (ireq.element) {
+ case choice_DigestReqInner_init: {
+ unsigned char server_nonce[16], identifier;
+
+ RAND_pseudo_bytes(&identifier, sizeof(identifier));
+ RAND_pseudo_bytes(server_nonce, sizeof(server_nonce));
+
+ server_nonce[0] = kdc_time & 0xff;
+ server_nonce[1] = (kdc_time >> 8) & 0xff;
+ server_nonce[2] = (kdc_time >> 16) & 0xff;
+ server_nonce[3] = (kdc_time >> 24) & 0xff;
+
+ r.element = choice_DigestRepInner_initReply;
+
+ hex_encode(server_nonce, sizeof(server_nonce), &r.u.initReply.nonce);
+ if (r.u.initReply.nonce == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "Failed to decode server nonce");
+ goto out;
+ }
+
+ sp = krb5_storage_emem();
+ if (sp == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+ ret = krb5_store_stringz(sp, ireq.u.init.type);
+ if (ret) {
+ krb5_clear_error_message(context);
+ goto out;
+ }
+
+ if (ireq.u.init.channel) {
+ char *s;
+ int aret;
+
+ aret = asprintf(&s, "%s-%s:%s", r.u.initReply.nonce,
+ ireq.u.init.channel->cb_type,
+ ireq.u.init.channel->cb_binding);
+ if (aret == -1 || s == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret,
+ "Failed to allocate channel binding");
+ goto out;
+ }
+ free(r.u.initReply.nonce);
+ r.u.initReply.nonce = s;
+ }
+
+ ret = krb5_store_stringz(sp, r.u.initReply.nonce);
+ if (ret) {
+ krb5_clear_error_message(context);
+ goto out;
+ }
+
+ if (strcasecmp(ireq.u.init.type, "CHAP") == 0) {
+ int aret;
+
+ r.u.initReply.identifier =
+ malloc(sizeof(*r.u.initReply.identifier));
+ if (r.u.initReply.identifier == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+
+ aret = asprintf(r.u.initReply.identifier, "%02X", identifier&0xff);
+ if (aret == -1 || *r.u.initReply.identifier == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+
+ } else
+ r.u.initReply.identifier = NULL;
+
+ if (ireq.u.init.hostname) {
+ ret = krb5_store_stringz(sp, *ireq.u.init.hostname);
+ if (ret) {
+ krb5_clear_error_message(context);
+ goto out;
+ }
+ }
+
+ ret = krb5_storage_to_data(sp, &buf);
+ if (ret) {
+ krb5_clear_error_message(context);
+ goto out;
+ }
+
+ ret = get_digest_key(context, config, server, &crypto);
+ if (ret)
+ goto out;
+
+ ret = krb5_create_checksum(context,
+ crypto,
+ KRB5_KU_DIGEST_OPAQUE,
+ 0,
+ buf.data,
+ buf.length,
+ &res);
+ krb5_crypto_destroy(context, crypto);
+ crypto = NULL;
+ krb5_data_free(&buf);
+ if (ret)
+ goto out;
+
+ ASN1_MALLOC_ENCODE(Checksum, buf.data, buf.length, &res, &size, ret);
+ free_Checksum(&res);
+ if (ret) {
+ krb5_set_error_message(context, ret, "Failed to encode "
+ "checksum in digest request");
+ goto out;
+ }
+ if (size != buf.length)
+ krb5_abortx(context, "ASN1 internal error");
+
+ hex_encode(buf.data, buf.length, &r.u.initReply.opaque);
+ free(buf.data);
+ krb5_data_zero(&buf);
+ if (r.u.initReply.opaque == NULL) {
+ krb5_clear_error_message(context);
+ ret = ENOMEM;
+ goto out;
+ }
+
+ kdc_log(context, config, 3, "Digest %s init request successful from %s",
+ ireq.u.init.type, from);
+
+ break;
+ }
+ case choice_DigestReqInner_digestRequest: {
+ sp = krb5_storage_emem();
+ if (sp == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+ ret = krb5_store_stringz(sp, ireq.u.digestRequest.type);
+ if (ret) {
+ krb5_clear_error_message(context);
+ goto out;
+ }
+
+ krb5_store_stringz(sp, ireq.u.digestRequest.serverNonce);
+
+ if (ireq.u.digestRequest.hostname) {
+ ret = krb5_store_stringz(sp, *ireq.u.digestRequest.hostname);
+ if (ret) {
+ krb5_clear_error_message(context);
+ goto out;
+ }
+ }
+
+ buf.length = strlen(ireq.u.digestRequest.opaque);
+ buf.data = malloc(buf.length);
+ if (buf.data == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+
+ ret = hex_decode(ireq.u.digestRequest.opaque, buf.data, buf.length);
+ if (ret <= 0) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "Failed to decode opaque");
+ goto out;
+ }
+ buf.length = ret;
+
+ ret = decode_Checksum(buf.data, buf.length, &res, NULL);
+ free(buf.data);
+ krb5_data_zero(&buf);
+ if (ret) {
+ krb5_set_error_message(context, ret,
+ "Failed to decode digest Checksum");
+ goto out;
+ }
+
+ ret = krb5_storage_to_data(sp, &buf);
+ if (ret) {
+ krb5_clear_error_message(context);
+ goto out;
+ }
+
+ serverNonce.length = strlen(ireq.u.digestRequest.serverNonce);
+ serverNonce.data = malloc(serverNonce.length);
+ if (serverNonce.data == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+
+ /*
+ * CHAP does the checksum of the raw nonce, but do it for all
+ * types, since we need to check the timestamp.
+ */
+ {
+ ssize_t ssize;
+
+ ssize = hex_decode(ireq.u.digestRequest.serverNonce,
+ serverNonce.data, serverNonce.length);
+ if (ssize <= 0) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "Failed to decode serverNonce");
+ goto out;
+ }
+ serverNonce.length = ssize;
+ }
+
+ ret = get_digest_key(context, config, server, &crypto);
+ if (ret)
+ goto out;
+
+ ret = _kdc_verify_checksum(context, crypto,
+ KRB5_KU_DIGEST_OPAQUE,
+ &buf, &res);
+ free_Checksum(&res);
+ krb5_data_free(&buf);
+ krb5_crypto_destroy(context, crypto);
+ crypto = NULL;
+ if (ret)
+ goto out;
+
+ /* verify time */
+ {
+ unsigned char *p = serverNonce.data;
+ uint32_t t;
+
+ if (serverNonce.length < 4) {
+ ret = EINVAL;
+ krb5_set_error_message(context, ret, "server nonce too short");
+ goto out;
+ }
+ t = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
+
+ if (labs((kdc_time & 0xffffffff) - t) > context->max_skew) {
+ ret = EINVAL;
+ krb5_set_error_message(context, ret, "time screw in server nonce ");
+ goto out;
+ }
+ }
+
+ if (strcasecmp(ireq.u.digestRequest.type, "CHAP") == 0) {
+ EVP_MD_CTX *ctx;
+ unsigned char md[MD5_DIGEST_LENGTH];
+ char *mdx;
+ char idx;
+
+ if ((config->digests_allowed & CHAP_MD5) == 0) {
+ kdc_log(context, config, 2, "Digest CHAP MD5 not allowed");
+ goto out;
+ }
+
+ if (ireq.u.digestRequest.identifier == NULL) {
+ ret = EINVAL;
+ krb5_set_error_message(context, ret, "Identifier missing "
+ "from CHAP request");
+ goto out;
+ }
+
+ if (hex_decode(*ireq.u.digestRequest.identifier, &idx, 1) != 1) {
+ ret = EINVAL;
+ krb5_set_error_message(context, ret, "failed to decode identifier");
+ goto out;
+ }
+
+ ret = get_password_entry(context, config,
+ ireq.u.digestRequest.username,
+ &password);
+ if (ret)
+ goto out;
+
+ ctx = EVP_MD_CTX_create();
+
+ EVP_DigestInit_ex(ctx, EVP_md5(), NULL);
+ EVP_DigestUpdate(ctx, &idx, 1);
+ EVP_DigestUpdate(ctx, password, strlen(password));
+ EVP_DigestUpdate(ctx, serverNonce.data, serverNonce.length);
+ EVP_DigestFinal_ex(ctx, md, NULL);
+
+ EVP_MD_CTX_destroy(ctx);
+
+ hex_encode(md, sizeof(md), &mdx);
+ if (mdx == NULL) {
+ krb5_clear_error_message(context);
+ ret = ENOMEM;
+ goto out;
+ }
+
+ r.element = choice_DigestRepInner_response;
+
+ ret = strcasecmp(mdx, ireq.u.digestRequest.responseData);
+ free(mdx);
+ if (ret == 0) {
+ r.u.response.success = TRUE;
+ } else {
+ kdc_log(context, config, 2,
+ "CHAP reply mismatch for %s",
+ ireq.u.digestRequest.username);
+ r.u.response.success = FALSE;
+ }
+
+ } else if (strcasecmp(ireq.u.digestRequest.type, "SASL-DIGEST-MD5") == 0) {
+ EVP_MD_CTX *ctx;
+ unsigned char md[MD5_DIGEST_LENGTH];
+ char *mdx;
+ char *A1, *A2;
+
+ if ((config->digests_allowed & DIGEST_MD5) == 0) {
+ kdc_log(context, config, 2, "Digest SASL MD5 not allowed");
+ goto out;
+ }
+
+ if (ireq.u.digestRequest.nonceCount == NULL)
+ goto out;
+ if (ireq.u.digestRequest.clientNonce == NULL)
+ goto out;
+ if (ireq.u.digestRequest.qop == NULL)
+ goto out;
+ if (ireq.u.digestRequest.realm == NULL)
+ goto out;
+
+ ret = get_password_entry(context, config,
+ ireq.u.digestRequest.username,
+ &password);
+ if (ret)
+ goto failed;
+
+ ctx = EVP_MD_CTX_create();
+
+ EVP_DigestInit_ex(ctx, EVP_md5(), NULL);
+ EVP_DigestUpdate(ctx, ireq.u.digestRequest.username,
+ strlen(ireq.u.digestRequest.username));
+ EVP_DigestUpdate(ctx, ":", 1);
+ EVP_DigestUpdate(ctx, *ireq.u.digestRequest.realm,
+ strlen(*ireq.u.digestRequest.realm));
+ EVP_DigestUpdate(ctx, ":", 1);
+ EVP_DigestUpdate(ctx, password, strlen(password));
+ EVP_DigestFinal_ex(ctx, md, NULL);
+
+ EVP_DigestInit_ex(ctx, EVP_md5(), NULL);
+ EVP_DigestUpdate(ctx, md, sizeof(md));
+ EVP_DigestUpdate(ctx, ":", 1);
+ EVP_DigestUpdate(ctx, ireq.u.digestRequest.serverNonce,
+ strlen(ireq.u.digestRequest.serverNonce));
+ EVP_DigestUpdate(ctx, ":", 1);
+ EVP_DigestUpdate(ctx, *ireq.u.digestRequest.nonceCount,
+ strlen(*ireq.u.digestRequest.nonceCount));
+ if (ireq.u.digestRequest.authid) {
+ EVP_DigestUpdate(ctx, ":", 1);
+ EVP_DigestUpdate(ctx, *ireq.u.digestRequest.authid,
+ strlen(*ireq.u.digestRequest.authid));
+ }
+ EVP_DigestFinal_ex(ctx, md, NULL);
+ hex_encode(md, sizeof(md), &A1);
+ if (A1 == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ EVP_MD_CTX_destroy(ctx);
+ goto failed;
+ }
+
+ EVP_DigestInit_ex(ctx, EVP_md5(), NULL);
+ EVP_DigestUpdate(ctx,
+ "AUTHENTICATE:", sizeof("AUTHENTICATE:") - 1);
+ EVP_DigestUpdate(ctx, *ireq.u.digestRequest.uri,
+ strlen(*ireq.u.digestRequest.uri));
+
+ /* conf|int */
+ if (strcmp(ireq.u.digestRequest.digest, "clear") != 0) {
+ static char conf_zeros[] = ":00000000000000000000000000000000";
+ EVP_DigestUpdate(ctx, conf_zeros, sizeof(conf_zeros) - 1);
+ }
+
+ EVP_DigestFinal_ex(ctx, md, NULL);
+
+ hex_encode(md, sizeof(md), &A2);
+ if (A2 == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ free(A1);
+ goto failed;
+ }
+
+ EVP_DigestInit_ex(ctx, EVP_md5(), NULL);
+ EVP_DigestUpdate(ctx, A1, strlen(A2));
+ EVP_DigestUpdate(ctx, ":", 1);
+ EVP_DigestUpdate(ctx, ireq.u.digestRequest.serverNonce,
+ strlen(ireq.u.digestRequest.serverNonce));
+ EVP_DigestUpdate(ctx, ":", 1);
+ EVP_DigestUpdate(ctx, *ireq.u.digestRequest.nonceCount,
+ strlen(*ireq.u.digestRequest.nonceCount));
+ EVP_DigestUpdate(ctx, ":", 1);
+ EVP_DigestUpdate(ctx, *ireq.u.digestRequest.clientNonce,
+ strlen(*ireq.u.digestRequest.clientNonce));
+ EVP_DigestUpdate(ctx, ":", 1);
+ EVP_DigestUpdate(ctx, *ireq.u.digestRequest.qop,
+ strlen(*ireq.u.digestRequest.qop));
+ EVP_DigestUpdate(ctx, ":", 1);
+ EVP_DigestUpdate(ctx, A2, strlen(A2));
+
+ EVP_DigestFinal_ex(ctx, md, NULL);
+
+ EVP_MD_CTX_destroy(ctx);
+
+ free(A1);
+ free(A2);
+
+ hex_encode(md, sizeof(md), &mdx);
+ if (mdx == NULL) {
+ krb5_clear_error_message(context);
+ ret = ENOMEM;
+ goto out;
+ }
+
+ r.element = choice_DigestRepInner_response;
+ ret = strcasecmp(mdx, ireq.u.digestRequest.responseData);
+ free(mdx);
+ if (ret == 0) {
+ r.u.response.success = TRUE;
+ } else {
+ kdc_log(context, config, 2,
+ "DIGEST-MD5 reply mismatch for %s",
+ ireq.u.digestRequest.username);
+ r.u.response.success = FALSE;
+ }
+
+ } else if (strcasecmp(ireq.u.digestRequest.type, "MS-CHAP-V2") == 0) {
+ unsigned char md[SHA_DIGEST_LENGTH], challenge[SHA_DIGEST_LENGTH];
+ krb5_principal clientprincipal = NULL;
+ char *mdx;
+ const char *username;
+ struct ntlm_buf answer;
+ Key *key = NULL;
+ EVP_MD_CTX *ctp;
+
+ if ((config->digests_allowed & MS_CHAP_V2) == 0) {
+ kdc_log(context, config, 2, "MS-CHAP-V2 not allowed");
+ goto failed;
+ }
+
+ if (ireq.u.digestRequest.clientNonce == NULL) {
+ ret = EINVAL;
+ krb5_set_error_message(context, ret,
+ "MS-CHAP-V2 clientNonce missing");
+ goto failed;
+ }
+ if (serverNonce.length != 16) {
+ ret = EINVAL;
+ krb5_set_error_message(context, ret,
+ "MS-CHAP-V2 serverNonce wrong length");
+ goto failed;
+ }
+
+ /* strip of the domain component */
+ username = strchr(ireq.u.digestRequest.username, '\\');
+ if (username == NULL)
+ username = ireq.u.digestRequest.username;
+ else
+ username++;
+
+ ctp = EVP_MD_CTX_create();
+
+ /* ChallengeHash */
+ EVP_DigestInit_ex(ctp, EVP_sha1(), NULL);
+ {
+ ssize_t ssize;
+ krb5_data clientNonce;
+
+ clientNonce.length = strlen(*ireq.u.digestRequest.clientNonce);
+ clientNonce.data = malloc(clientNonce.length);
+ if (clientNonce.data == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret,
+ "malloc: out of memory");
+ EVP_MD_CTX_destroy(ctp);
+ goto out;
+ }
+
+ ssize = hex_decode(*ireq.u.digestRequest.clientNonce,
+ clientNonce.data, clientNonce.length);
+ if (ssize != 16) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret,
+ "Failed to decode clientNonce");
+ EVP_MD_CTX_destroy(ctp);
+ goto out;
+ }
+ EVP_DigestUpdate(ctp, clientNonce.data, ssize);
+ free(clientNonce.data);
+ }
+ EVP_DigestUpdate(ctp, serverNonce.data, serverNonce.length);
+ EVP_DigestUpdate(ctp, username, strlen(username));
+
+ EVP_DigestFinal_ex(ctp, challenge, NULL);
+
+ EVP_MD_CTX_destroy(ctp);
+
+ /* NtPasswordHash */
+ ret = krb5_parse_name(context, username, &clientprincipal);
+ if (ret)
+ goto failed;
+
+ ret = _kdc_db_fetch(context, config, clientprincipal,
+ HDB_F_GET_CLIENT, NULL, &userdb, &user);
+ krb5_free_principal(context, clientprincipal);
+ if (ret) {
+ krb5_set_error_message(context, ret,
+ "MS-CHAP-V2 user %s not in database",
+ username);
+ goto failed;
+ }
+
+ ret = hdb_enctype2key(context, user, NULL,
+ ETYPE_ARCFOUR_HMAC_MD5, &key);
+ if (ret) {
+ krb5_set_error_message(context, ret,
+ "MS-CHAP-V2 missing arcfour key %s",
+ username);
+ goto failed;
+ }
+
+ /* ChallengeResponse */
+ ret = heim_ntlm_calculate_ntlm1(key->key.keyvalue.data,
+ key->key.keyvalue.length,
+ challenge, &answer);
+ if (ret) {
+ krb5_set_error_message(context, ret, "NTLM missing arcfour key");
+ goto failed;
+ }
+
+ hex_encode(answer.data, answer.length, &mdx);
+ if (mdx == NULL) {
+ free(answer.data);
+ krb5_clear_error_message(context);
+ ret = ENOMEM;
+ goto out;
+ }
+
+ r.element = choice_DigestRepInner_response;
+ ret = strcasecmp(mdx, ireq.u.digestRequest.responseData);
+ if (ret == 0) {
+ r.u.response.success = TRUE;
+ } else {
+ kdc_log(context, config, 2,
+ "MS-CHAP-V2 hash mismatch for %s",
+ ireq.u.digestRequest.username);
+ r.u.response.success = FALSE;
+ }
+ free(mdx);
+
+ if (r.u.response.success) {
+ unsigned char hashhash[MD4_DIGEST_LENGTH];
+ EVP_MD_CTX *ctxp;
+
+ ctxp = EVP_MD_CTX_create();
+
+ /* hashhash */
+ {
+ EVP_DigestInit_ex(ctxp, EVP_md4(), NULL);
+ EVP_DigestUpdate(ctxp,
+ key->key.keyvalue.data,
+ key->key.keyvalue.length);
+ EVP_DigestFinal_ex(ctxp, hashhash, NULL);
+ }
+
+ /* GenerateAuthenticatorResponse */
+ EVP_DigestInit_ex(ctxp, EVP_sha1(), NULL);
+ EVP_DigestUpdate(ctxp, hashhash, sizeof(hashhash));
+ EVP_DigestUpdate(ctxp, answer.data, answer.length);
+ EVP_DigestUpdate(ctxp, ms_chap_v2_magic1,
+ sizeof(ms_chap_v2_magic1));
+ EVP_DigestFinal_ex(ctxp, md, NULL);
+
+ EVP_DigestInit_ex(ctxp, EVP_sha1(), NULL);
+ EVP_DigestUpdate(ctxp, md, sizeof(md));
+ EVP_DigestUpdate(ctxp, challenge, 8);
+ EVP_DigestUpdate(ctxp, ms_chap_v2_magic2,
+ sizeof(ms_chap_v2_magic2));
+ EVP_DigestFinal_ex(ctxp, md, NULL);
+
+ r.u.response.rsp = calloc(1, sizeof(*r.u.response.rsp));
+ if (r.u.response.rsp == NULL) {
+ free(answer.data);
+ krb5_clear_error_message(context);
+ EVP_MD_CTX_destroy(ctxp);
+ ret = ENOMEM;
+ goto out;
+ }
+
+ hex_encode(md, sizeof(md), r.u.response.rsp);
+ if (r.u.response.rsp == NULL) {
+ free(answer.data);
+ krb5_clear_error_message(context);
+ EVP_MD_CTX_destroy(ctxp);
+ ret = ENOMEM;
+ goto out;
+ }
+
+ /* get_master, rfc 3079 3.4 */
+ EVP_DigestInit_ex(ctxp, EVP_sha1(), NULL);
+ EVP_DigestUpdate(ctxp, hashhash, 16);
+ EVP_DigestUpdate(ctxp, answer.data, answer.length);
+ EVP_DigestUpdate(ctxp, ms_rfc3079_magic1,
+ sizeof(ms_rfc3079_magic1));
+ EVP_DigestFinal_ex(ctxp, md, NULL);
+
+ free(answer.data);
+
+ EVP_MD_CTX_destroy(ctxp);
+
+ r.u.response.session_key =
+ calloc(1, sizeof(*r.u.response.session_key));
+ if (r.u.response.session_key == NULL) {
+ krb5_clear_error_message(context);
+ ret = ENOMEM;
+ goto out;
+ }
+
+ ret = krb5_data_copy(r.u.response.session_key, md, 16);
+ if (ret) {
+ krb5_clear_error_message(context);
+ goto out;
+ }
+ }
+
+ } else {
+ int aret;
+
+ r.element = choice_DigestRepInner_error;
+ aret = asprintf(&r.u.error.reason, "Unsupported digest type %s",
+ ireq.u.digestRequest.type);
+ if (aret == -1 || r.u.error.reason == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+ r.u.error.code = EINVAL;
+ }
+
+ kdc_log(context, config, 3, "Digest %s request successful %s",
+ ireq.u.digestRequest.type, ireq.u.digestRequest.username);
+
+ break;
+ }
+ case choice_DigestReqInner_ntlmInit:
+
+ if ((config->digests_allowed & (NTLM_V1|NTLM_V1_SESSION|NTLM_V2)) == 0) {
+ kdc_log(context, config, 2, "NTLM not allowed");
+ goto failed;
+ }
+
+ r.element = choice_DigestRepInner_ntlmInitReply;
+
+ r.u.ntlmInitReply.flags = NTLM_NEG_UNICODE;
+
+ if ((ireq.u.ntlmInit.flags & NTLM_NEG_UNICODE) == 0) {
+ kdc_log(context, config, 2, "NTLM client have no unicode");
+ goto failed;
+ }
+
+ if (ireq.u.ntlmInit.flags & NTLM_NEG_NTLM)
+ r.u.ntlmInitReply.flags |= NTLM_NEG_NTLM;
+ else {
+ kdc_log(context, config, 2, "NTLM client doesn't support NTLM");
+ goto failed;
+ }
+
+ r.u.ntlmInitReply.flags |=
+ NTLM_NEG_TARGET |
+ NTLM_TARGET_DOMAIN |
+ NTLM_ENC_128;
+
+#define ALL \
+ NTLM_NEG_SIGN| \
+ NTLM_NEG_SEAL| \
+ NTLM_NEG_ALWAYS_SIGN| \
+ NTLM_NEG_NTLM2_SESSION| \
+ NTLM_NEG_KEYEX
+
+ r.u.ntlmInitReply.flags |= (ireq.u.ntlmInit.flags & (ALL));
+
+#undef ALL
+
+ r.u.ntlmInitReply.targetname =
+ get_ntlm_targetname(context, client);
+ if (r.u.ntlmInitReply.targetname == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+ r.u.ntlmInitReply.challenge.data = malloc(8);
+ if (r.u.ntlmInitReply.challenge.data == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+ r.u.ntlmInitReply.challenge.length = 8;
+ if (RAND_bytes(r.u.ntlmInitReply.challenge.data,
+ r.u.ntlmInitReply.challenge.length) != 1)
+ {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "out of random error");
+ goto out;
+ }
+ /* XXX fix targetinfo */
+ ALLOC(r.u.ntlmInitReply.targetinfo);
+ if (r.u.ntlmInitReply.targetinfo == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+
+ ret = fill_targetinfo(context,
+ r.u.ntlmInitReply.targetname,
+ client,
+ r.u.ntlmInitReply.targetinfo);
+ if (ret) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+
+ /*
+ * Save data encryted in opaque for the second part of the
+ * ntlm authentication
+ */
+ sp = krb5_storage_emem();
+ if (sp == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+
+ ret = krb5_storage_write(sp, r.u.ntlmInitReply.challenge.data, 8);
+ if (ret != 8) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "storage write challenge");
+ goto out;
+ }
+ ret = krb5_store_uint32(sp, r.u.ntlmInitReply.flags);
+ if (ret) {
+ krb5_clear_error_message(context);
+ goto out;
+ }
+
+ ret = krb5_storage_to_data(sp, &buf);
+ if (ret) {
+ krb5_clear_error_message(context);
+ goto out;
+ }
+
+ ret = get_digest_key(context, config, server, &crypto);
+ if (ret)
+ goto out;
+
+ ret = krb5_encrypt(context, crypto, KRB5_KU_DIGEST_OPAQUE,
+ buf.data, buf.length, &r.u.ntlmInitReply.opaque);
+ krb5_data_free(&buf);
+ krb5_crypto_destroy(context, crypto);
+ crypto = NULL;
+ if (ret)
+ goto out;
+
+ kdc_log(context, config, 3, "NTLM init from %s", from);
+
+ break;
+
+ case choice_DigestReqInner_ntlmRequest: {
+ krb5_principal clientprincipal;
+ unsigned char sessionkey[16];
+ unsigned char challenge[8];
+ uint32_t flags;
+ Key *key = NULL;
+ int version;
+
+ r.element = choice_DigestRepInner_ntlmResponse;
+ r.u.ntlmResponse.success = 0;
+ r.u.ntlmResponse.flags = 0;
+ r.u.ntlmResponse.sessionkey = NULL;
+ r.u.ntlmResponse.tickets = NULL;
+
+ /* get username */
+ ret = krb5_parse_name(context,
+ ireq.u.ntlmRequest.username,
+ &clientprincipal);
+ if (ret)
+ goto failed;
+
+ ret = _kdc_db_fetch(context, config, clientprincipal,
+ HDB_F_GET_CLIENT, NULL, &userdb, &user);
+ krb5_free_principal(context, clientprincipal);
+ if (ret) {
+ krb5_set_error_message(context, ret, "NTLM user %s not in database",
+ ireq.u.ntlmRequest.username);
+ goto failed;
+ }
+
+ ret = get_digest_key(context, config, server, &crypto);
+ if (ret)
+ goto failed;
+
+ ret = krb5_decrypt(context, crypto, KRB5_KU_DIGEST_OPAQUE,
+ ireq.u.ntlmRequest.opaque.data,
+ ireq.u.ntlmRequest.opaque.length, &buf);
+ krb5_crypto_destroy(context, crypto);
+ crypto = NULL;
+ if (ret) {
+ kdc_log(context, config, 2,
+ "Failed to decrypt nonce from %s", from);
+ goto failed;
+ }
+
+ sp = krb5_storage_from_data(&buf);
+ if (sp == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+
+ ret = krb5_storage_read(sp, challenge, sizeof(challenge));
+ if (ret != sizeof(challenge)) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "NTLM storage read challenge");
+ goto out;
+ }
+ ret = krb5_ret_uint32(sp, &flags);
+ if (ret) {
+ krb5_set_error_message(context, ret, "NTLM storage read flags");
+ goto out;
+ }
+ krb5_storage_free(sp);
+ sp = NULL;
+ krb5_data_free(&buf);
+
+ if ((flags & NTLM_NEG_NTLM) == 0) {
+ ret = EINVAL;
+ krb5_set_error_message(context, ret, "NTLM not negotiated");
+ goto out;
+ }
+
+ ret = hdb_enctype2key(context, user, NULL,
+ ETYPE_ARCFOUR_HMAC_MD5, &key);
+ if (ret) {
+ krb5_set_error_message(context, ret, "NTLM missing arcfour key");
+ goto out;
+ }
+
+ /* check if this is NTLMv2 */
+ if (ireq.u.ntlmRequest.ntlm.length != 24) {
+ struct ntlm_buf infotarget, answer;
+ char *targetname;
+
+ if ((config->digests_allowed & NTLM_V2) == 0) {
+ kdc_log(context, config, 2, "NTLM v2 not allowed");
+ goto out;
+ }
+
+ version = 2;
+
+ targetname = get_ntlm_targetname(context, client);
+ if (targetname == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+
+ answer.length = ireq.u.ntlmRequest.ntlm.length;
+ answer.data = ireq.u.ntlmRequest.ntlm.data;
+
+ ret = heim_ntlm_verify_ntlm2(key->key.keyvalue.data,
+ key->key.keyvalue.length,
+ ireq.u.ntlmRequest.username,
+ targetname,
+ 0,
+ challenge,
+ &answer,
+ &infotarget,
+ sessionkey);
+ free(targetname);
+ if (ret) {
+ krb5_set_error_message(context, ret, "NTLM v2 verify failed");
+ goto failed;
+ }
+
+ /* XXX verify infotarget matches client (checksum ?) */
+
+ free(infotarget.data);
+ /* */
+
+ } else {
+ struct ntlm_buf answer;
+
+ version = 1;
+
+ if (flags & NTLM_NEG_NTLM2_SESSION) {
+ unsigned char sessionhash[MD5_DIGEST_LENGTH];
+ EVP_MD_CTX *ctx;
+
+ if ((config->digests_allowed & NTLM_V1_SESSION) == 0) {
+ kdc_log(context, config, 2, "NTLM v1-session not allowed");
+ ret = EINVAL;
+ goto failed;
+ }
+
+ if (ireq.u.ntlmRequest.lm.length != 24) {
+ ret = EINVAL;
+ krb5_set_error_message(context, ret, "LM hash have wrong length "
+ "for NTLM session key");
+ goto failed;
+ }
+
+ ctx = EVP_MD_CTX_create();
+
+ EVP_DigestInit_ex(ctx, EVP_md5(), NULL);
+
+ EVP_DigestUpdate(ctx, challenge, sizeof(challenge));
+ EVP_DigestUpdate(ctx, ireq.u.ntlmRequest.lm.data, 8);
+ EVP_DigestFinal_ex(ctx, sessionhash, NULL);
+ memcpy(challenge, sessionhash, sizeof(challenge));
+
+ EVP_MD_CTX_destroy(ctx);
+
+ } else {
+ if ((config->digests_allowed & NTLM_V1) == 0) {
+ kdc_log(context, config, 2, "NTLM v1 not allowed");
+ goto failed;
+ }
+ }
+
+ ret = heim_ntlm_calculate_ntlm1(key->key.keyvalue.data,
+ key->key.keyvalue.length,
+ challenge, &answer);
+ if (ret) {
+ krb5_set_error_message(context, ret, "NTLM missing arcfour key");
+ goto failed;
+ }
+
+ if (ireq.u.ntlmRequest.ntlm.length != answer.length ||
+ ct_memcmp(ireq.u.ntlmRequest.ntlm.data, answer.data, answer.length) != 0)
+ {
+ free(answer.data);
+ ret = EINVAL;
+ krb5_set_error_message(context, ret, "NTLM hash mismatch");
+ goto failed;
+ }
+ free(answer.data);
+
+ {
+ EVP_MD_CTX *ctx;
+
+ ctx = EVP_MD_CTX_create();
+
+ EVP_DigestInit_ex(ctx, EVP_md4(), NULL);
+ EVP_DigestUpdate(ctx,
+ key->key.keyvalue.data,
+ key->key.keyvalue.length);
+ EVP_DigestFinal_ex(ctx, sessionkey, NULL);
+
+ EVP_MD_CTX_destroy(ctx);
+ }
+ }
+
+ if (ireq.u.ntlmRequest.sessionkey) {
+ unsigned char masterkey[MD4_DIGEST_LENGTH];
+ EVP_CIPHER_CTX rc4;
+ size_t len;
+
+ if ((flags & NTLM_NEG_KEYEX) == 0) {
+ ret = EINVAL;
+ krb5_set_error_message(context, ret,
+ "NTLM client failed to neg key "
+ "exchange but still sent key");
+ goto failed;
+ }
+
+ len = ireq.u.ntlmRequest.sessionkey->length;
+ if (len != sizeof(masterkey)){
+ ret = EINVAL;
+ krb5_set_error_message(context, ret,
+ "NTLM master key wrong length: %lu",
+ (unsigned long)len);
+ goto failed;
+ }
+
+
+ EVP_CIPHER_CTX_init(&rc4);
+ EVP_CipherInit_ex(&rc4, EVP_rc4(), NULL, sessionkey, NULL, 1);
+ EVP_Cipher(&rc4,
+ masterkey, ireq.u.ntlmRequest.sessionkey->data,
+ sizeof(masterkey));
+ EVP_CIPHER_CTX_cleanup(&rc4);
+
+ r.u.ntlmResponse.sessionkey =
+ malloc(sizeof(*r.u.ntlmResponse.sessionkey));
+ if (r.u.ntlmResponse.sessionkey == NULL) {
+ ret = EINVAL;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+
+ ret = krb5_data_copy(r.u.ntlmResponse.sessionkey,
+ masterkey, sizeof(masterkey));
+ if (ret) {
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+ }
+
+ r.u.ntlmResponse.success = 1;
+ kdc_log(context, config, 0, "NTLM version %d successful for %s",
+ version, ireq.u.ntlmRequest.username);
+ break;
+ }
+ case choice_DigestReqInner_supportedMechs:
+
+ kdc_log(context, config, 4, "digest supportedMechs from %s", from);
+
+ r.element = choice_DigestRepInner_supportedMechs;
+ memset(&r.u.supportedMechs, 0, sizeof(r.u.supportedMechs));
+
+ if (config->digests_allowed & NTLM_V1)
+ r.u.supportedMechs.ntlm_v1 = 1;
+ if (config->digests_allowed & NTLM_V1_SESSION)
+ r.u.supportedMechs.ntlm_v1_session = 1;
+ if (config->digests_allowed & NTLM_V2)
+ r.u.supportedMechs.ntlm_v2 = 1;
+ if (config->digests_allowed & DIGEST_MD5)
+ r.u.supportedMechs.digest_md5 = 1;
+ if (config->digests_allowed & CHAP_MD5)
+ r.u.supportedMechs.chap_md5 = 1;
+ if (config->digests_allowed & MS_CHAP_V2)
+ r.u.supportedMechs.ms_chap_v2 = 1;
+ break;
+
+ default: {
+ const char *s;
+ ret = EINVAL;
+ krb5_set_error_message(context, ret, "unknown operation to digest");
+
+ failed:
+
+ s = krb5_get_error_message(context, ret);
+ if (s == NULL) {
+ krb5_clear_error_message(context);
+ goto out;
+ }
+
+ kdc_log(context, config, 2, "Digest failed with: %s", s);
+
+ r.element = choice_DigestRepInner_error;
+ r.u.error.reason = strdup("unknown error");
+ krb5_free_error_message(context, s);
+ if (r.u.error.reason == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+ r.u.error.code = EINVAL;
+ break;
+ }
+ }
+
+ ASN1_MALLOC_ENCODE(DigestRepInner, buf.data, buf.length, &r, &size, ret);
+ if (ret) {
+ krb5_set_error_message(context, ret, "Failed to encode inner digest reply");
+ goto out;
+ }
+ if (size != buf.length)
+ krb5_abortx(context, "ASN1 internal error");
+
+ krb5_auth_con_addflags(context, ac, KRB5_AUTH_CONTEXT_USE_SUBKEY, NULL);
+
+ ret = krb5_mk_rep (context, ac, &rep.apRep);
+ if (ret)
+ goto out;
+
+ {
+ krb5_keyblock *key;
+
+ ret = krb5_auth_con_getlocalsubkey(context, ac, &key);
+ if (ret)
+ goto out;
+
+ ret = krb5_crypto_init(context, key, 0, &crypto);
+ krb5_free_keyblock (context, key);
+ if (ret)
+ goto out;
+ }
+
+ ret = krb5_encrypt_EncryptedData(context, crypto, KRB5_KU_DIGEST_ENCRYPT,
+ buf.data, buf.length, 0,
+ &rep.innerRep);
+ if (ret) {
+ krb5_prepend_error_message(context, ret, "Failed to encrypt digest: ");
+ goto out;
+ }
+
+ ASN1_MALLOC_ENCODE(DigestREP, reply->data, reply->length, &rep, &size, ret);
+ if (ret) {
+ krb5_set_error_message(context, ret, "Failed to encode digest reply");
+ goto out;
+ }
+ if (size != reply->length)
+ krb5_abortx(context, "ASN1 internal error");
+
+
+ out:
+ if (ac)
+ krb5_auth_con_free(context, ac);
+ if (ret)
+ krb5_warn(context, ret, "Digest request from %s failed", from);
+ if (ticket)
+ krb5_free_ticket(context, ticket);
+ if (id)
+ krb5_kt_close(context, id);
+ if (crypto)
+ krb5_crypto_destroy(context, crypto);
+ if (sp)
+ krb5_storage_free(sp);
+ if (user)
+ _kdc_free_ent (context, userdb, user);
+ if (server)
+ _kdc_free_ent (context, serverdb, server);
+ if (client)
+ _kdc_free_ent (context, clientdb, client);
+ if (password) {
+ memset(password, 0, strlen(password));
+ free (password);
+ }
+ if (client_name)
+ free (client_name);
+ krb5_data_free(&buf);
+ krb5_data_free(&serverNonce);
+ free_Checksum(&res);
+ free_DigestREP(&rep);
+ free_DigestRepInner(&r);
+ free_DigestReqInner(&ireq);
+
+ return ret;
+}
+
+#endif /* DIGEST */
diff --git a/third_party/heimdal/kdc/fast.c b/third_party/heimdal/kdc/fast.c
new file mode 100644
index 0000000..bc77f74
--- /dev/null
+++ b/third_party/heimdal/kdc/fast.c
@@ -0,0 +1,1002 @@
+/*
+ * Copyright (c) 1997-2011 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Portions Copyright (c) 2010 - 2011 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "kdc_locl.h"
+
+static krb5_error_code
+salt_fastuser_crypto(astgs_request_t r,
+ krb5_const_principal salt_principal,
+ krb5_enctype enctype,
+ krb5_crypto fast_crypto,
+ krb5_crypto *salted_crypto)
+{
+ krb5_error_code ret;
+ krb5_principal client_princ = NULL;
+ krb5_data salt;
+ krb5_keyblock dkey;
+ size_t size;
+
+ *salted_crypto = NULL;
+
+ krb5_data_zero(&salt);
+ krb5_keyblock_zero(&dkey);
+
+ if (salt_principal == NULL) {
+ if (r->req.req_body.cname == NULL) {
+ ret = KRB5KRB_ERR_GENERIC;
+ goto out;
+ }
+
+ ret = _krb5_principalname2krb5_principal(r->context, &client_princ,
+ *(r->req.req_body.cname),
+ r->req.req_body.realm);
+ if (ret)
+ goto out;
+
+ salt_principal = client_princ;
+ }
+
+ ret = krb5_unparse_name(r->context, salt_principal, (char **)&salt.data);
+ if (ret)
+ goto out;
+
+ salt.length = strlen(salt.data);
+
+ kdc_log(r->context, r->config, 10,
+ "salt_fastuser_crypto: salt principal is %s (%d)",
+ (char *)salt.data, enctype);
+
+ ret = krb5_enctype_keysize(r->context, enctype, &size);
+ if (ret)
+ goto out;
+
+ ret = krb5_crypto_prfplus(r->context, fast_crypto, &salt,
+ size, &dkey.keyvalue);
+ if (ret)
+ goto out;
+
+ dkey.keytype = enctype;
+
+ ret = krb5_crypto_init(r->context, &dkey, ENCTYPE_NULL, salted_crypto);
+ if (ret)
+ goto out;
+
+out:
+ krb5_free_keyblock_contents(r->context, &dkey);
+ krb5_data_free(&salt);
+ krb5_free_principal(r->context, client_princ);
+
+ return ret;
+}
+
+static krb5_error_code
+get_fastuser_crypto(astgs_request_t r,
+ krb5_const_principal ticket_client,
+ krb5_enctype enctype,
+ krb5_crypto *crypto)
+{
+ krb5_principal fast_princ;
+ HDB *fast_db;
+ hdb_entry *fast_user = NULL;
+ Key *cookie_key = NULL;
+ krb5_crypto fast_crypto = NULL;
+ krb5_error_code ret;
+
+ *crypto = NULL;
+
+ ret = krb5_make_principal(r->context, &fast_princ,
+ KRB5_WELLKNOWN_ORG_H5L_REALM,
+ KRB5_WELLKNOWN_NAME, "org.h5l.fast-cookie", NULL);
+ if (ret)
+ goto out;
+
+ ret = _kdc_db_fetch(r->context, r->config, fast_princ,
+ HDB_F_GET_FAST_COOKIE, NULL, &fast_db, &fast_user);
+ if (ret)
+ goto out;
+
+ if (enctype == KRB5_ENCTYPE_NULL)
+ ret = _kdc_get_preferred_key(r->context, r->config, fast_user,
+ "fast-cookie", &enctype, &cookie_key);
+ else
+ ret = hdb_enctype2key(r->context, fast_user, NULL,
+ enctype, &cookie_key);
+ if (ret)
+ goto out;
+
+ ret = krb5_crypto_init(r->context, &cookie_key->key,
+ ENCTYPE_NULL, &fast_crypto);
+ if (ret)
+ goto out;
+
+ ret = salt_fastuser_crypto(r, ticket_client,
+ cookie_key->key.keytype,
+ fast_crypto, crypto);
+ if (ret)
+ goto out;
+
+ out:
+ if (fast_user)
+ _kdc_free_ent(r->context, fast_db, fast_user);
+ if (fast_crypto)
+ krb5_crypto_destroy(r->context, fast_crypto);
+ krb5_free_principal(r->context, fast_princ);
+
+ return ret;
+}
+
+
+static krb5_error_code
+fast_parse_cookie(astgs_request_t r,
+ krb5_const_principal ticket_client,
+ const PA_DATA *pa)
+{
+ krb5_crypto crypto = NULL;
+ krb5_error_code ret;
+ KDCFastCookie data;
+ krb5_data d1;
+ size_t len;
+
+ ret = decode_KDCFastCookie(pa->padata_value.data,
+ pa->padata_value.length,
+ &data, &len);
+ if (ret)
+ return ret;
+
+ if (len != pa->padata_value.length || strcmp("H5L1", data.version) != 0) {
+ free_KDCFastCookie(&data);
+ return KRB5KDC_ERR_POLICY;
+ }
+
+ ret = get_fastuser_crypto(r, ticket_client, data.cookie.etype, &crypto);
+ if (ret)
+ goto out;
+
+ ret = krb5_decrypt_EncryptedData(r->context, crypto,
+ KRB5_KU_H5L_COOKIE,
+ &data.cookie, &d1);
+ krb5_crypto_destroy(r->context, crypto);
+ if (ret)
+ goto out;
+
+ ret = decode_KDCFastState(d1.data, d1.length, &r->fast, &len);
+ krb5_data_free(&d1);
+ if (ret)
+ goto out;
+
+ if (r->fast.expiration < kdc_time) {
+ kdc_log(r->context, r->config, 2, "FAST cookie expired");
+ ret = KRB5KDC_ERR_POLICY;
+ goto out;
+ }
+
+ out:
+ free_KDCFastCookie(&data);
+
+ return ret;
+}
+
+static krb5_error_code
+fast_add_cookie(astgs_request_t r,
+ krb5_const_principal ticket_client,
+ METHOD_DATA *method_data)
+{
+ krb5_crypto crypto = NULL;
+ KDCFastCookie shell;
+ krb5_error_code ret;
+ krb5_data data;
+ size_t size;
+
+ memset(&shell, 0, sizeof(shell));
+
+ r->fast.expiration = kdc_time + FAST_EXPIRATION_TIME;
+
+ ASN1_MALLOC_ENCODE(KDCFastState, data.data, data.length,
+ &r->fast, &size, ret);
+ if (ret)
+ return ret;
+ heim_assert(size == data.length, "internal asn.1 encoder error");
+
+ ret = get_fastuser_crypto(r, ticket_client, KRB5_ENCTYPE_NULL, &crypto);
+ if (ret) {
+ kdc_log(r->context, r->config, 0,
+ "Failed to find FAST principal for cookie encryption: %d", ret);
+ goto out;
+ }
+
+ ret = krb5_encrypt_EncryptedData(r->context, crypto,
+ KRB5_KU_H5L_COOKIE,
+ data.data, data.length, 0,
+ &shell.cookie);
+ krb5_crypto_destroy(r->context, crypto);
+ if (ret)
+ goto out;
+
+ krb5_data_free(&data);
+
+ shell.version = "H5L1";
+
+ ASN1_MALLOC_ENCODE(KDCFastCookie, data.data, data.length,
+ &shell, &size, ret);
+ free_EncryptedData(&shell.cookie);
+ if (ret)
+ goto out;
+ heim_assert(size == data.length, "internal asn.1 encoder error");
+
+ ret = krb5_padata_add(r->context, method_data,
+ KRB5_PADATA_FX_COOKIE,
+ data.data, data.length);
+ if (ret == 0)
+ krb5_data_zero(&data);
+
+ out:
+ krb5_data_free(&data);
+ return ret;
+}
+
+static krb5_error_code
+fast_add_dummy_cookie(astgs_request_t r,
+ METHOD_DATA *method_data)
+{
+ krb5_error_code ret;
+ krb5_data data;
+ const krb5_data *dummy_fast_cookie = &r->config->dummy_fast_cookie;
+
+ if (dummy_fast_cookie->data == NULL)
+ return 0;
+
+ ret = krb5_data_copy(&data,
+ dummy_fast_cookie->data,
+ dummy_fast_cookie->length);
+ if (ret)
+ return ret;
+
+ ret = krb5_padata_add(r->context, method_data,
+ KRB5_PADATA_FX_COOKIE,
+ data.data, data.length);
+ if (ret) {
+ krb5_data_free(&data);
+ }
+
+ return ret;
+}
+
+krb5_error_code
+_kdc_fast_mk_response(krb5_context context,
+ krb5_crypto armor_crypto,
+ METHOD_DATA *pa_data,
+ krb5_keyblock *strengthen_key,
+ KrbFastFinished *finished,
+ krb5uint32 nonce,
+ krb5_data *data)
+{
+ PA_FX_FAST_REPLY fxfastrep;
+ KrbFastResponse fastrep;
+ krb5_error_code ret;
+ krb5_data buf;
+ size_t size;
+
+ memset(&fxfastrep, 0, sizeof(fxfastrep));
+ memset(&fastrep, 0, sizeof(fastrep));
+ krb5_data_zero(data);
+
+ if (pa_data) {
+ fastrep.padata.val = pa_data->val;
+ fastrep.padata.len = pa_data->len;
+ }
+ fastrep.strengthen_key = strengthen_key;
+ fastrep.finished = finished;
+ fastrep.nonce = nonce;
+
+ ASN1_MALLOC_ENCODE(KrbFastResponse, buf.data, buf.length,
+ &fastrep, &size, ret);
+ if (ret)
+ return ret;
+ heim_assert(size == buf.length, "internal asn.1 encoder error");
+
+ fxfastrep.element = choice_PA_FX_FAST_REPLY_armored_data;
+
+ ret = krb5_encrypt_EncryptedData(context,
+ armor_crypto,
+ KRB5_KU_FAST_REP,
+ buf.data,
+ buf.length,
+ 0,
+ &fxfastrep.u.armored_data.enc_fast_rep);
+ krb5_data_free(&buf);
+ if (ret)
+ return ret;
+
+ ASN1_MALLOC_ENCODE(PA_FX_FAST_REPLY, data->data, data->length,
+ &fxfastrep, &size, ret);
+ free_PA_FX_FAST_REPLY(&fxfastrep);
+ if (ret)
+ return ret;
+ heim_assert(size == data->length, "internal asn.1 encoder error");
+
+ return 0;
+}
+
+
+static krb5_error_code
+_kdc_fast_mk_e_data(astgs_request_t r,
+ METHOD_DATA *error_method,
+ krb5_crypto armor_crypto,
+ const KDC_REQ_BODY *req_body,
+ krb5_error_code outer_error,
+ krb5_principal error_client,
+ krb5_principal error_server,
+ time_t *csec, int *cusec,
+ krb5_data *e_data)
+{
+ krb5_error_code ret = 0;
+ size_t size;
+
+ /*
+ * FX-COOKIE can be used outside of FAST, e.g. SRP or GSS.
+ */
+ if (armor_crypto || r->fast.fast_state.len) {
+ if (r->config->enable_fast_cookie) {
+ kdc_log(r->context, r->config, 5, "Adding FAST cookie for KRB-ERROR");
+ ret = fast_add_cookie(r, error_client, error_method);
+ if (ret) {
+ kdc_log(r->context, r->config, 1,
+ "Failed to add FAST cookie: %d", ret);
+ free_METHOD_DATA(error_method);
+ return ret;
+ }
+ } else {
+ kdc_log(r->context, r->config, 5, "Adding dummy FAST cookie for KRB-ERROR");
+ ret = fast_add_dummy_cookie(r, error_method);
+ if (ret) {
+ kdc_log(r->context, r->config, 1,
+ "Failed to add dummy FAST cookie: %d", ret);
+ free_METHOD_DATA(error_method);
+ return ret;
+ }
+ }
+ }
+
+ if (armor_crypto) {
+ PA_FX_FAST_REPLY fxfastrep;
+ KrbFastResponse fastrep;
+
+ memset(&fxfastrep, 0, sizeof(fxfastrep));
+ memset(&fastrep, 0, sizeof(fastrep));
+
+ kdc_log(r->context, r->config, 5, "Making FAST inner KRB-ERROR");
+
+ /* first add the KRB-ERROR to the fast errors */
+
+ ret = krb5_mk_error(r->context,
+ outer_error,
+ r->e_text,
+ NULL,
+ error_client,
+ error_server,
+ csec,
+ cusec,
+ e_data);
+ if (ret) {
+ kdc_log(r->context, r->config, 1,
+ "Failed to make inner KRB-ERROR: %d", ret);
+ return ret;
+ }
+
+ ret = krb5_padata_add(r->context, error_method,
+ KRB5_PADATA_FX_ERROR,
+ e_data->data, e_data->length);
+ if (ret) {
+ kdc_log(r->context, r->config, 1,
+ "Failed to make add FAST PADATA to inner KRB-ERROR: %d", ret);
+ krb5_data_free(e_data);
+ return ret;
+ }
+
+ r->e_text = NULL;
+
+ ret = _kdc_fast_mk_response(r->context, armor_crypto,
+ error_method, NULL, NULL,
+ req_body->nonce, e_data);
+ free_METHOD_DATA(error_method);
+ if (ret) {
+ kdc_log(r->context, r->config, 1,
+ "Failed to make outer KRB-ERROR: %d", ret);
+ return ret;
+ }
+
+ ret = krb5_padata_add(r->context, error_method,
+ KRB5_PADATA_FX_FAST,
+ e_data->data, e_data->length);
+ if (ret) {
+ kdc_log(r->context, r->config, 1,
+ "Failed to make add FAST PADATA to outer KRB-ERROR: %d", ret);
+ return ret;
+ }
+ } else
+ kdc_log(r->context, r->config, 5, "Making non-FAST KRB-ERROR");
+
+ if (error_method && error_method->len) {
+ ASN1_MALLOC_ENCODE(METHOD_DATA, e_data->data, e_data->length,
+ error_method, &size, ret);
+ if (ret) {
+ kdc_log(r->context, r->config, 1,
+ "Failed to make encode METHOD-DATA: %d", ret);
+ return ret;
+ }
+ heim_assert(size == e_data->length, "internal asn.1 encoder error");
+ }
+
+ return ret;
+}
+
+
+krb5_error_code
+_kdc_fast_mk_error(astgs_request_t r,
+ METHOD_DATA *error_method,
+ krb5_crypto armor_crypto,
+ const KDC_REQ_BODY *req_body,
+ krb5_error_code outer_error,
+ krb5_principal error_client,
+ krb5_principal error_server,
+ time_t *csec, int *cusec,
+ krb5_data *error_msg)
+{
+ krb5_error_code ret;
+ krb5_data _e_data;
+ krb5_data *e_data = NULL;
+
+ krb5_data_zero(&_e_data);
+
+ heim_assert(r != NULL, "invalid request in _kdc_fast_mk_error");
+
+ if (r->e_data.length) {
+ e_data = &r->e_data;
+ } else {
+ ret = _kdc_fast_mk_e_data(r,
+ error_method,
+ armor_crypto,
+ req_body,
+ outer_error,
+ error_client,
+ error_server,
+ csec, cusec,
+ &_e_data);
+ if (ret) {
+ kdc_log(r->context, r->config, 1,
+ "Failed to make FAST e-data: %d", ret);
+ return ret;
+ }
+
+ e_data = &_e_data;
+ }
+
+ if (armor_crypto) {
+ if (r->fast.flags.requested_hidden_names) {
+ error_client = NULL;
+ error_server = NULL;
+ }
+ csec = NULL;
+ cusec = NULL;
+ }
+
+ ret = krb5_mk_error(r->context,
+ outer_error,
+ r->e_text,
+ (e_data->length ? e_data : NULL),
+ error_client,
+ error_server,
+ csec,
+ cusec,
+ error_msg);
+ krb5_data_free(&_e_data);
+
+ if (ret)
+ kdc_log(r->context, r->config, 1,
+ "Failed to make encode KRB-ERROR: %d", ret);
+
+ return ret;
+}
+
+static krb5_error_code
+fast_unwrap_request(astgs_request_t r,
+ krb5_ticket *tgs_ticket,
+ krb5_auth_context tgs_ac)
+{
+ krb5_principal armor_server_principal = NULL;
+ char *armor_client_principal_name = NULL;
+ char *armor_server_principal_name = NULL;
+ PA_FX_FAST_REQUEST fxreq;
+ krb5_auth_context ac = NULL;
+ krb5_ticket *ticket = NULL;
+ krb5_flags ap_req_options;
+ krb5_keyblock armorkey;
+ krb5_keyblock explicit_armorkey;
+ krb5_error_code ret;
+ krb5_ap_req ap_req;
+ KrbFastReq fastreq;
+ const PA_DATA *pa;
+ krb5_data data;
+ size_t len;
+ int i = 0;
+
+ memset(&fxreq, 0, sizeof(fxreq));
+ memset(&fastreq, 0, sizeof(fastreq));
+
+ pa = _kdc_find_padata(&r->req, &i, KRB5_PADATA_FX_FAST);
+ if (pa == NULL) {
+ if (tgs_ac && r->fast_asserted) {
+ kdc_log(r->context, r->config, 1,
+ "Client asserted FAST but did not include FX-FAST pa-data");
+ ret = KRB5KRB_AP_ERR_MODIFIED;
+ goto out;
+ }
+
+ kdc_log(r->context, r->config, 10, "Not a FAST request");
+ return 0;
+ }
+
+ ret = decode_PA_FX_FAST_REQUEST(pa->padata_value.data,
+ pa->padata_value.length,
+ &fxreq,
+ &len);
+ if (ret) {
+ kdc_log(r->context, r->config, 4,
+ "Failed to decode PA-FX-FAST-REQUEST: %d", ret);
+ goto out;
+ }
+
+ if (fxreq.element != choice_PA_FX_FAST_REQUEST_armored_data) {
+ kdc_log(r->context, r->config, 4,
+ "PA-FX-FAST-REQUEST contains unknown type: %d",
+ (int)fxreq.element);
+ ret = KRB5KDC_ERR_PREAUTH_FAILED;
+ goto out;
+ }
+
+ /*
+ * If check for armor data or it's not a TGS-REQ with implicit
+ * armor.
+ */
+ if (fxreq.u.armored_data.armor == NULL && tgs_ac == NULL) {
+ kdc_log(r->context, r->config, 4,
+ "AS-REQ armor missing");
+ ret = KRB5KDC_ERR_PREAUTH_FAILED;
+ goto out;
+ }
+
+ r->explicit_armor_present = fxreq.u.armored_data.armor != NULL && tgs_ac != NULL;
+
+ /*
+ *
+ */
+ if (fxreq.u.armored_data.armor != NULL) {
+ krb5uint32 kvno;
+ krb5uint32 *kvno_ptr = NULL;
+
+ if (fxreq.u.armored_data.armor->armor_type != 1) {
+ kdc_log(r->context, r->config, 4,
+ "Incorrect AS-REQ armor type");
+ ret = KRB5KDC_ERR_PREAUTH_FAILED;
+ goto out;
+ }
+
+ ret = krb5_decode_ap_req(r->context,
+ &fxreq.u.armored_data.armor->armor_value,
+ &ap_req);
+ if(ret) {
+ kdc_log(r->context, r->config, 4, "Failed to decode AP-REQ");
+ goto out;
+ }
+
+ /* Save that principal that was in the request */
+ ret = _krb5_principalname2krb5_principal(r->context,
+ &armor_server_principal,
+ ap_req.ticket.sname,
+ ap_req.ticket.realm);
+ if (ret) {
+ free_AP_REQ(&ap_req);
+ goto out;
+ }
+
+ if (ap_req.ticket.enc_part.kvno != NULL) {
+ kvno = *ap_req.ticket.enc_part.kvno;
+ kvno_ptr = &kvno;
+ }
+
+ ret = _kdc_db_fetch(r->context, r->config, armor_server_principal,
+ HDB_F_GET_KRBTGT | HDB_F_DELAY_NEW_KEYS,
+ kvno_ptr,
+ &r->armor_serverdb, &r->armor_server);
+ if(ret == HDB_ERR_NOT_FOUND_HERE) {
+ free_AP_REQ(&ap_req);
+ kdc_log(r->context, r->config, 5,
+ "Armor key does not have secrets at this KDC, "
+ "need to proxy");
+ goto out;
+ } else if (ret) {
+ free_AP_REQ(&ap_req);
+ ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
+ goto out;
+ }
+
+ ret = hdb_enctype2key(r->context, r->armor_server, NULL,
+ ap_req.ticket.enc_part.etype,
+ &r->armor_key);
+ if (ret) {
+ free_AP_REQ(&ap_req);
+ goto out;
+ }
+
+ ret = krb5_verify_ap_req2(r->context, &ac,
+ &ap_req,
+ armor_server_principal,
+ &r->armor_key->key,
+ 0,
+ &ap_req_options,
+ &r->armor_ticket,
+ KRB5_KU_AP_REQ_AUTH);
+ free_AP_REQ(&ap_req);
+ if (ret)
+ goto out;
+
+ ret = krb5_unparse_name(r->context, armor_server_principal,
+ &armor_server_principal_name);
+ if (ret)
+ goto out;
+
+ /* FIXME krb5_verify_ap_req2() also checks this */
+ ret = _kdc_verify_flags(r->context, r->config,
+ &r->armor_ticket->ticket,
+ armor_server_principal_name);
+ if (ret) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "Armor TGT expired or invalid");
+ goto out;
+ }
+ ticket = r->armor_ticket;
+ } else {
+ heim_assert(tgs_ticket != NULL, "TGS authentication context without ticket");
+ ac = tgs_ac;
+ ticket = tgs_ticket;
+ }
+
+ (void) krb5_unparse_name(r->context, ticket->client, &armor_client_principal_name);
+ kdc_audit_addkv((kdc_request_t)r, 0, "armor_client_name", "%s",
+ armor_client_principal_name ?
+ armor_client_principal_name :
+ "<out of memory>");
+
+ if (ac->remote_subkey == NULL) {
+ krb5_auth_con_free(r->context, ac);
+ kdc_log(r->context, r->config, 2,
+ "FAST AP-REQ remote subkey missing");
+ ret = KRB5KDC_ERR_PREAUTH_FAILED;
+ goto out;
+ }
+
+ r->fast.flags.kdc_verified =
+ !_kdc_is_anonymous_pkinit(r->context, ticket->client);
+
+ ret = _krb5_fast_armor_key(r->context,
+ ac->remote_subkey,
+ &ticket->ticket.key,
+ &armorkey,
+ r->explicit_armor_present ? NULL : &r->armor_crypto);
+ if (ret)
+ goto out;
+
+ if (r->explicit_armor_present) {
+ ret = _krb5_fast_explicit_armor_key(r->context,
+ &armorkey,
+ tgs_ac->remote_subkey,
+ &explicit_armorkey,
+ &r->armor_crypto);
+ if (ret)
+ goto out;
+
+ krb5_free_keyblock_contents(r->context, &explicit_armorkey);
+ }
+
+ krb5_free_keyblock_contents(r->context, &armorkey);
+
+ ret = krb5_decrypt_EncryptedData(r->context, r->armor_crypto,
+ KRB5_KU_FAST_ENC,
+ &fxreq.u.armored_data.enc_fast_req,
+ &data);
+ if (ret) {
+ kdc_log(r->context, r->config, 2,
+ "Failed to decrypt FAST request");
+ goto out;
+ }
+
+ ret = decode_KrbFastReq(data.data, data.length, &fastreq, NULL);
+ krb5_data_free(&data);
+ if (ret)
+ goto out;
+
+ /*
+ * verify req-checksum of the outer body
+ */
+ if (tgs_ac) {
+ /*
+ * -- For TGS, contains the checksum performed over the type
+ * -- AP-REQ in the PA-TGS-REQ padata.
+ */
+ i = 0;
+ pa = _kdc_find_padata(&r->req, &i, KRB5_PADATA_TGS_REQ);
+ if (pa == NULL) {
+ kdc_log(r->context, r->config, 4,
+ "FAST TGS request missing TGS-REQ padata");
+ ret = KRB5KRB_ERR_GENERIC;
+ goto out;
+ }
+
+ ret = _kdc_verify_checksum(r->context, r->armor_crypto,
+ KRB5_KU_FAST_REQ_CHKSUM,
+ &pa->padata_value,
+ &fxreq.u.armored_data.req_checksum);
+ if (ret) {
+ kdc_log(r->context, r->config, 2,
+ "Bad checksum in FAST TGS request");
+ goto out;
+ }
+ } else {
+ /*
+ * -- For AS, contains the checksum performed over the type
+ * -- KDC-REQ-BODY for the req-body field of the KDC-REQ
+ * -- structure;
+ */
+ ret = _kdc_verify_checksum(r->context, r->armor_crypto,
+ KRB5_KU_FAST_REQ_CHKSUM,
+ &r->req.req_body._save,
+ &fxreq.u.armored_data.req_checksum);
+ if (ret) {
+ kdc_log(r->context, r->config, 2,
+ "Bad checksum in FAST AS request");
+ goto out;
+ }
+ }
+
+ /*
+ * check for unsupported mandatory options
+ */
+ if (FastOptions2int(fastreq.fast_options) & 0xfffc) {
+ kdc_log(r->context, r->config, 2,
+ "FAST unsupported mandatory option set");
+ ret = KRB5_KDC_ERR_UNKNOWN_CRITICAL_FAST_OPTIONS;
+ goto out;
+ }
+
+ r->fast.flags.requested_hidden_names = fastreq.fast_options.hide_client_names;
+
+ /* KDC MUST ignore outer pa data preauth-14 - 6.5.5 */
+ if (r->req.padata)
+ free_METHOD_DATA(r->req.padata);
+ else
+ ALLOC(r->req.padata);
+
+ ret = copy_METHOD_DATA(&fastreq.padata, r->req.padata);
+ if (ret)
+ goto out;
+
+ free_KDC_REQ_BODY(&r->req.req_body);
+ ret = copy_KDC_REQ_BODY(&fastreq.req_body, &r->req.req_body);
+ if (ret)
+ goto out;
+
+ kdc_log(r->context, r->config, 5, "Client selected FAST");
+
+ out:
+ if (ac && ac != tgs_ac)
+ krb5_auth_con_free(r->context, ac);
+
+ krb5_free_principal(r->context, armor_server_principal);
+ krb5_xfree(armor_client_principal_name);
+ krb5_xfree(armor_server_principal_name);
+
+ free_KrbFastReq(&fastreq);
+ free_PA_FX_FAST_REQUEST(&fxreq);
+
+ return ret;
+}
+
+/*
+ *
+ */
+krb5_error_code
+_kdc_fast_unwrap_request(astgs_request_t r,
+ krb5_ticket *tgs_ticket,
+ krb5_auth_context tgs_ac)
+{
+ krb5_error_code ret;
+ const PA_DATA *pa;
+ int i = 0;
+
+ if (!r->config->enable_fast)
+ return 0;
+
+ ret = fast_unwrap_request(r, tgs_ticket, tgs_ac);
+ if (ret)
+ return ret;
+
+ if (r->config->enable_fast_cookie) {
+ /*
+ * FX-COOKIE can be used outside of FAST, e.g. SRP or GSS.
+ */
+ pa = _kdc_find_padata(&r->req, &i, KRB5_PADATA_FX_COOKIE);
+ if (pa) {
+ krb5_const_principal ticket_client = NULL;
+
+ if (tgs_ticket)
+ ticket_client = tgs_ticket->client;
+
+ ret = fast_parse_cookie(r, ticket_client, pa);
+ }
+ }
+
+ return ret;
+}
+
+/*
+ * Strengthen reply key by mixing with a random key that is
+ * protected by FAST.
+ */
+krb5_error_code
+_kdc_fast_strengthen_reply_key(astgs_request_t r)
+{
+ if (r->armor_crypto) {
+ krb5_keyblock new_reply_key;
+ krb5_error_code ret;
+
+ kdc_log(r->context, r->config, 5,
+ "FAST strengthen reply key with strengthen-key");
+
+ heim_assert(r->reply_key.keytype != KRB5_ENCTYPE_NULL, "NULL reply key enctype");
+
+ ret = krb5_generate_random_keyblock(r->context, r->reply_key.keytype,
+ &r->strengthen_key);
+ if (ret) {
+ kdc_log(r->context, r->config, 0, "failed to prepare random keyblock");
+ return ret;
+ }
+
+ ret = _krb5_fast_cf2(r->context,
+ &r->strengthen_key, "strengthenkey",
+ &r->reply_key, "replykey",
+ &new_reply_key, NULL);
+ if (ret)
+ return ret;
+
+ krb5_free_keyblock_contents(r->context, &r->reply_key);
+ r->reply_key = new_reply_key;
+ }
+
+ return 0;
+}
+
+/*
+ * Zero and free KDCFastState
+ */
+void
+_kdc_free_fast_state(KDCFastState *state)
+{
+ size_t i;
+
+ for (i = 0; i < state->fast_state.len; i++) {
+ PA_DATA *pa = &state->fast_state.val[i];
+
+ if (pa->padata_value.data)
+ memset_s(pa->padata_value.data, 0,
+ pa->padata_value.length, pa->padata_value.length);
+ }
+ free_KDCFastState(state);
+}
+
+krb5_error_code
+_kdc_fast_check_armor_pac(astgs_request_t r, int flags)
+{
+ krb5_error_code ret;
+ krb5_boolean ad_kdc_issued = FALSE;
+ krb5_pac mspac = NULL;
+ krb5_principal armor_client_principal = NULL;
+ HDB *armor_db;
+ hdb_entry *armor_client = NULL;
+ char *armor_client_principal_name = NULL;
+
+ flags |= HDB_F_ARMOR_PRINCIPAL;
+ if (_kdc_synthetic_princ_used_p(r->context, r->armor_ticket))
+ flags |= HDB_F_SYNTHETIC_OK;
+ if (r->req.req_body.kdc_options.canonicalize)
+ flags |= HDB_F_CANON;
+
+ ret = _krb5_principalname2krb5_principal(r->context,
+ &armor_client_principal,
+ r->armor_ticket->ticket.cname,
+ r->armor_ticket->ticket.crealm);
+ if (ret)
+ goto out;
+
+ ret = krb5_unparse_name(r->context, armor_client_principal,
+ &armor_client_principal_name);
+ if (ret)
+ goto out;
+
+ ret = _kdc_db_fetch_client(r->context, r->config, flags,
+ armor_client_principal, armor_client_principal_name,
+ r->req.req_body.realm, &armor_db, &armor_client);
+ if (ret)
+ goto out;
+
+ ret = kdc_check_flags(r, FALSE, armor_client, NULL);
+ if (ret)
+ goto out;
+
+ ret = _kdc_check_pac(r, armor_client_principal, NULL,
+ armor_client, r->armor_server,
+ r->armor_server, r->armor_server,
+ &r->armor_key->key, &r->armor_key->key,
+ &r->armor_ticket->ticket, &ad_kdc_issued, &mspac, NULL, NULL);
+ if (ret) {
+ const char *msg = krb5_get_error_message(r->context, ret);
+
+ kdc_log(r->context, r->config, 4,
+ "Verify armor PAC (%s) failed for %s (%s) from %s with %s (%s)",
+ armor_client_principal_name, r->cname, r->sname,
+ r->from, msg, mspac ? "Ticket unsigned" : "No PAC");
+
+ krb5_free_error_message(r->context, msg);
+
+ goto out;
+ }
+
+ r->armor_clientdb = armor_db;
+ armor_db = NULL;
+
+ r->armor_client = armor_client;
+ armor_client = NULL;
+
+ r->armor_pac = mspac;
+ mspac = NULL;
+
+out:
+ krb5_xfree(armor_client_principal_name);
+ if (armor_client)
+ _kdc_free_ent(r->context, armor_db, armor_client);
+ krb5_free_principal(r->context, armor_client_principal);
+ krb5_pac_free(r->context, mspac);
+
+ return ret;
+}
diff --git a/third_party/heimdal/kdc/gss_preauth.c b/third_party/heimdal/kdc/gss_preauth.c
new file mode 100644
index 0000000..24663de
--- /dev/null
+++ b/third_party/heimdal/kdc/gss_preauth.c
@@ -0,0 +1,1034 @@
+/*
+ * Copyright (c) 2021, PADL Software Pty Ltd.
+ * All rights reserved.
+ *
+ * Portions Copyright (c) 2019 Kungliga Tekniska Högskolan
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of PADL Software nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY PADL SOFTWARE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL PADL SOFTWARE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "kdc_locl.h"
+
+#include <gssapi/gssapi.h>
+#include <gssapi_mech.h>
+
+#include <gss-preauth-protos.h>
+#include <gss-preauth-private.h>
+
+#include "gss_preauth_authorizer_plugin.h"
+
+struct gss_client_params {
+ OM_uint32 major, minor;
+ gss_ctx_id_t context_handle;
+ gss_name_t initiator_name;
+ gss_OID mech_type;
+ gss_buffer_desc output_token;
+ OM_uint32 flags;
+ OM_uint32 lifetime;
+ krb5_checksum req_body_checksum;
+};
+
+static void
+pa_gss_display_status(astgs_request_t r,
+ OM_uint32 major,
+ OM_uint32 minor,
+ gss_client_params *gcp,
+ const char *msg);
+
+static void
+pa_gss_display_name(gss_name_t name,
+ gss_buffer_t namebuf,
+ gss_const_buffer_t *namebuf_p);
+
+static void HEIM_CALLCONV
+pa_gss_dealloc_client_params(void *ptr);
+
+/*
+ * Create a checksum over KDC-REQ-BODY (without the nonce), used to
+ * assert the request is invariant within the preauth conversation.
+ */
+static krb5_error_code
+pa_gss_create_req_body_checksum(astgs_request_t r,
+ krb5_checksum *checksum)
+{
+ krb5_error_code ret;
+ KDC_REQ_BODY b = r->req.req_body;
+ krb5_data data;
+ size_t size;
+
+ b.nonce = 0;
+
+ ASN1_MALLOC_ENCODE(KDC_REQ_BODY, data.data, data.length, &b, &size, ret);
+ heim_assert(ret || data.length,
+ "internal asn1 encoder error");
+
+ ret = krb5_create_checksum(r->context, NULL, 0, CKSUMTYPE_SHA256,
+ data.data, data.length, checksum);
+ krb5_data_free(&data);
+
+ return ret;
+}
+
+/*
+ * Verify a checksum over KDC-REQ-BODY (without the nonce), used to
+ * assert the request is invariant within the preauth conversation.
+ */
+static krb5_error_code
+pa_gss_verify_req_body_checksum(astgs_request_t r,
+ krb5_checksum *checksum)
+{
+ krb5_error_code ret;
+ KDC_REQ_BODY b = r->req.req_body;
+ krb5_data data;
+ size_t size;
+
+ b.nonce = 0;
+
+ ASN1_MALLOC_ENCODE(KDC_REQ_BODY, data.data, data.length, &b, &size, ret);
+ heim_assert(ret || data.length,
+ "internal asn1 encoder error");
+
+ ret = _kdc_verify_checksum(r->context, NULL, 0, &data, checksum);
+ krb5_data_free(&data);
+
+ return ret;
+}
+
+/*
+ * Decode the FX-COOKIE context state, consisting of the exported
+ * GSS context token concatenated with the checksum of the initial
+ * KDC-REQ-BODY.
+ */
+static krb5_error_code
+pa_gss_decode_context_state(astgs_request_t r,
+ const krb5_data *state,
+ gss_buffer_t sec_context_token,
+ krb5_checksum *req_body_checksum)
+{
+ krb5_error_code ret;
+ krb5_storage *sp;
+ size_t cksumsize;
+ krb5_data data;
+ int32_t cksumtype;
+
+ memset(req_body_checksum, 0, sizeof(*req_body_checksum));
+ sec_context_token->length = 0;
+ sec_context_token->value = NULL;
+
+ krb5_data_zero(&data);
+
+ sp = krb5_storage_from_readonly_mem(state->data, state->length);
+ if (sp == NULL) {
+ ret = krb5_enomem(r->context);
+ goto out;
+ }
+
+ krb5_storage_set_eof_code(sp, KRB5_BAD_MSIZE);
+ krb5_storage_set_byteorder(sp, KRB5_STORAGE_BYTEORDER_PACKED);
+
+ ret = krb5_ret_data(sp, &data);
+ if (ret)
+ goto out;
+
+ ret = krb5_ret_int32(sp, &cksumtype);
+ if (ret)
+ goto out;
+
+ req_body_checksum->cksumtype = (CKSUMTYPE)cksumtype;
+
+ if (req_body_checksum->cksumtype == CKSUMTYPE_NONE ||
+ krb5_checksum_is_keyed(r->context, req_body_checksum->cksumtype)) {
+ ret = KRB5KDC_ERR_SUMTYPE_NOSUPP;
+ goto out;
+ }
+
+ ret = krb5_checksumsize(r->context, req_body_checksum->cksumtype,
+ &cksumsize);
+ if (ret)
+ goto out;
+
+ req_body_checksum->checksum.data = malloc(cksumsize);
+ if (req_body_checksum->checksum.data == NULL) {
+ ret = krb5_enomem(r->context);
+ goto out;
+ }
+
+ if (krb5_storage_read(sp, req_body_checksum->checksum.data,
+ cksumsize) != cksumsize) {
+ ret = KRB5_BAD_MSIZE;
+ goto out;
+ }
+
+ req_body_checksum->checksum.length = cksumsize;
+
+ _krb5_gss_data_to_buffer(&data, sec_context_token);
+
+out:
+ if (ret) {
+ krb5_data_free(&data);
+ free_Checksum(req_body_checksum);
+ memset(req_body_checksum, 0, sizeof(*req_body_checksum));
+ }
+ krb5_storage_free(sp);
+
+ return ret;
+}
+
+/*
+ * Deserialize a GSS-API security context from the FAST cookie.
+ */
+static krb5_error_code
+pa_gss_get_context_state(astgs_request_t r,
+ gss_client_params *gcp)
+{
+ int idx = 0;
+ PA_DATA *fast_pa;
+ krb5_error_code ret;
+
+ OM_uint32 major, minor;
+ gss_buffer_desc sec_context_token;
+
+ fast_pa = krb5_find_padata(r->fast.fast_state.val,
+ r->fast.fast_state.len,
+ KRB5_PADATA_GSS, &idx);
+ if (fast_pa == NULL)
+ return 0;
+
+ ret = pa_gss_decode_context_state(r, &fast_pa->padata_value,
+ &sec_context_token,
+ &gcp->req_body_checksum);
+ if (ret)
+ return ret;
+
+ ret = pa_gss_verify_req_body_checksum(r, &gcp->req_body_checksum);
+ if (ret) {
+ gss_release_buffer(&minor, &sec_context_token);
+ return ret;
+ }
+
+ major = gss_import_sec_context(&minor, &sec_context_token,
+ &gcp->context_handle);
+ if (GSS_ERROR(major)) {
+ pa_gss_display_status(r, major, minor, gcp,
+ "Failed to import GSS pre-authentication context");
+ ret = _krb5_gss_map_error(major, minor);
+ } else
+ ret = 0;
+
+ gss_release_buffer(&minor, &sec_context_token);
+
+ return ret;
+}
+
+/*
+ * Encode the FX-COOKIE context state, consisting of the exported
+ * GSS context token concatenated with the checksum of the initial
+ * KDC-REQ-BODY.
+ */
+static krb5_error_code
+pa_gss_encode_context_state(astgs_request_t r,
+ gss_const_buffer_t sec_context_token,
+ const krb5_checksum *req_body_checksum,
+ krb5_data *state)
+{
+ krb5_error_code ret;
+ krb5_storage *sp;
+ krb5_data data;
+
+ krb5_data_zero(state);
+
+ sp = krb5_storage_emem();
+ if (sp == NULL) {
+ ret = krb5_enomem(r->context);
+ goto out;
+ }
+
+ krb5_storage_set_byteorder(sp, KRB5_STORAGE_BYTEORDER_PACKED);
+
+ _krb5_gss_buffer_to_data(sec_context_token, &data);
+
+ ret = krb5_store_data(sp, data);
+ if (ret)
+ goto out;
+
+ ret = krb5_store_int32(sp, (int32_t)req_body_checksum->cksumtype);
+ if (ret)
+ goto out;
+
+ ret = krb5_store_bytes(sp, req_body_checksum->checksum.data,
+ req_body_checksum->checksum.length);
+ if (ret)
+ goto out;
+
+ ret = krb5_storage_to_data(sp, state);
+ if (ret)
+ goto out;
+
+out:
+ krb5_storage_free(sp);
+
+ return ret;
+}
+
+/*
+ * Serialize a GSS-API security context into a FAST cookie.
+ */
+static krb5_error_code
+pa_gss_set_context_state(astgs_request_t r,
+ gss_client_params *gcp)
+{
+ krb5_error_code ret;
+ PA_DATA *fast_pa;
+ int idx = 0;
+ krb5_data state;
+
+ OM_uint32 major, minor;
+ gss_buffer_desc sec_context_token = GSS_C_EMPTY_BUFFER;
+
+ /*
+ * On second and subsequent responses, we can recycle the checksum
+ * from the request as it is validated and invariant. This saves
+ * re-encoding the request body again.
+ */
+ if (gcp->req_body_checksum.cksumtype == CKSUMTYPE_NONE) {
+ ret = pa_gss_create_req_body_checksum(r, &gcp->req_body_checksum);
+ if (ret)
+ return ret;
+ }
+
+ major = gss_export_sec_context(&minor, &gcp->context_handle,
+ &sec_context_token);
+ if (GSS_ERROR(major)) {
+ pa_gss_display_status(r, major, minor, gcp,
+ "Failed to export GSS pre-authentication context");
+ return _krb5_gss_map_error(major, minor);
+ }
+
+ ret = pa_gss_encode_context_state(r, &sec_context_token,
+ &gcp->req_body_checksum, &state);
+ gss_release_buffer(&minor, &sec_context_token);
+ if (ret)
+ return ret;
+
+ fast_pa = krb5_find_padata(r->fast.fast_state.val,
+ r->fast.fast_state.len,
+ KRB5_PADATA_GSS, &idx);
+ if (fast_pa) {
+ krb5_data_free(&fast_pa->padata_value);
+ fast_pa->padata_value = state;
+ } else {
+ ret = krb5_padata_add(r->context, &r->fast.fast_state,
+ KRB5_PADATA_GSS,
+ state.data, state.length);
+ if (ret)
+ krb5_data_free(&state);
+ }
+
+ return ret;
+}
+
+static krb5_error_code
+pa_gss_acquire_acceptor_cred(astgs_request_t r,
+ gss_client_params *gcp,
+ gss_cred_id_t *cred)
+{
+ krb5_error_code ret;
+ krb5_principal tgs_name;
+
+ OM_uint32 major, minor;
+ gss_name_t target_name = GSS_C_NO_NAME;
+ gss_buffer_desc display_name = GSS_C_EMPTY_BUFFER;
+ gss_const_buffer_t display_name_p;
+
+ *cred = GSS_C_NO_CREDENTIAL;
+
+ ret = krb5_make_principal(r->context, &tgs_name, r->req.req_body.realm,
+ KRB5_TGS_NAME, r->req.req_body.realm, NULL);
+ if (ret)
+ return ret;
+
+ ret = _krb5_gss_pa_unparse_name(r->context, tgs_name, &target_name);
+ krb5_free_principal(r->context, tgs_name);
+ if (ret)
+ return ret;
+
+ pa_gss_display_name(target_name, &display_name, &display_name_p);
+
+ kdc_log(r->context, r->config, 4,
+ "Acquiring GSS acceptor credential for %.*s",
+ (int)display_name_p->length, (char *)display_name_p->value);
+
+ major = gss_acquire_cred(&minor, target_name, GSS_C_INDEFINITE,
+ r->config->gss_mechanisms_allowed,
+ GSS_C_ACCEPT, cred, NULL, NULL);
+ ret = _krb5_gss_map_error(major, minor);
+
+ if (ret)
+ pa_gss_display_status(r, major, minor, gcp,
+ "Failed to acquire GSS acceptor credential");
+
+ gss_release_buffer(&minor, &display_name);
+ gss_release_name(&minor, &target_name);
+
+ return ret;
+}
+
+krb5_error_code
+_kdc_gss_rd_padata(astgs_request_t r,
+ const PA_DATA *pa,
+ gss_client_params **pgcp,
+ int *open)
+{
+ krb5_error_code ret;
+
+ OM_uint32 minor;
+ gss_client_params *gcp = NULL;
+ gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
+ gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
+ struct gss_channel_bindings_struct cb;
+
+ memset(&cb, 0, sizeof(cb));
+
+ *pgcp = NULL;
+
+ if (!r->config->enable_gss_preauth) {
+ ret = KRB5KDC_ERR_POLICY;
+ goto out;
+ }
+
+ if (pa->padata_value.length == 0) {
+ ret = KRB5KDC_ERR_PREAUTH_FAILED;
+ goto out;
+ }
+
+ gcp = kdc_object_alloc(sizeof(*gcp), "pa-gss-client-params", pa_gss_dealloc_client_params);
+ if (gcp == NULL) {
+ ret = krb5_enomem(r->context);
+ goto out;
+ }
+
+ /* errors are fast fail until gss_accept_sec_context() is called */
+ gcp->major = GSS_S_NO_CONTEXT;
+
+ ret = pa_gss_get_context_state(r, gcp);
+ if (ret)
+ goto out;
+
+ ret = pa_gss_acquire_acceptor_cred(r, gcp, &cred);
+ if (ret)
+ goto out;
+
+ _krb5_gss_data_to_buffer(&pa->padata_value, &input_token);
+ _krb5_gss_data_to_buffer(&r->req.req_body._save, &cb.application_data);
+
+ gcp->major = gss_accept_sec_context(&gcp->minor,
+ &gcp->context_handle,
+ cred,
+ &input_token,
+ &cb,
+ &gcp->initiator_name,
+ &gcp->mech_type,
+ &gcp->output_token,
+ &gcp->flags,
+ &gcp->lifetime,
+ NULL); /* delegated_cred_handle */
+
+ ret = _krb5_gss_map_error(gcp->major, gcp->minor);
+
+ if (GSS_ERROR(gcp->major)) {
+ pa_gss_display_status(r, gcp->major, gcp->minor, gcp,
+ "Failed to accept GSS security context");
+ } else if ((gcp->flags & GSS_C_ANON_FLAG) && !_kdc_is_anon_request(&r->req)) {
+ kdc_log(r->context, r->config, 2,
+ "Anonymous GSS pre-authentication request w/o anonymous flag");
+ ret = KRB5KDC_ERR_BADOPTION;
+ } else
+ *open = (gcp->major == GSS_S_COMPLETE);
+
+out:
+ gss_release_cred(&minor, &cred);
+
+ if (gcp && gcp->major != GSS_S_NO_CONTEXT)
+ *pgcp = gcp;
+ else
+ kdc_object_release(gcp);
+
+ return ret;
+}
+
+krb5_timestamp
+_kdc_gss_endtime(astgs_request_t r,
+ gss_client_params *gcp)
+{
+ krb5_timestamp endtime;
+
+ if (gcp->lifetime == GSS_C_INDEFINITE)
+ endtime = 0;
+ else
+ endtime = kdc_time + gcp->lifetime;
+
+ kdc_log(r->context, r->config, 10,
+ "GSS pre-authentication endtime is %ld", (long)endtime);
+
+ return endtime;
+}
+
+struct pa_gss_authorize_plugin_ctx {
+ astgs_request_t r;
+ struct gss_client_params *gcp;
+ krb5_boolean authorized;
+ krb5_principal initiator_princ;
+};
+
+static krb5_error_code KRB5_LIB_CALL
+pa_gss_authorize_cb(krb5_context context,
+ const void *plug,
+ void *plugctx,
+ void *userctx)
+{
+ const krb5plugin_gss_preauth_authorizer_ftable *authorizer = plug;
+ struct pa_gss_authorize_plugin_ctx *pa_gss_authorize_plugin_ctx = userctx;
+
+ return authorizer->authorize(plugctx,
+ pa_gss_authorize_plugin_ctx->r,
+ pa_gss_authorize_plugin_ctx->gcp->initiator_name,
+ pa_gss_authorize_plugin_ctx->gcp->mech_type,
+ pa_gss_authorize_plugin_ctx->gcp->flags,
+ &pa_gss_authorize_plugin_ctx->authorized,
+ &pa_gss_authorize_plugin_ctx->initiator_princ);
+}
+
+static const char *plugin_deps[] = {
+ "kdc",
+ "hdb",
+ "gssapi",
+ "krb5",
+ NULL
+};
+
+static struct heim_plugin_data
+gss_preauth_authorizer_data = {
+ "kdc",
+ KDC_GSS_PREAUTH_AUTHORIZER,
+ KDC_GSS_PREAUTH_AUTHORIZER_VERSION_1,
+ plugin_deps,
+ kdc_get_instance
+};
+
+static krb5_error_code
+pa_gss_authorize_plugin(astgs_request_t r,
+ struct gss_client_params *gcp,
+ gss_const_buffer_t display_name,
+ krb5_boolean *authorized,
+ krb5_principal *initiator_princ)
+{
+ krb5_error_code ret;
+ struct pa_gss_authorize_plugin_ctx ctx;
+
+ ctx.r = r;
+ ctx.gcp = gcp;
+ ctx.authorized = 0;
+ ctx.initiator_princ = NULL;
+
+ krb5_clear_error_message(r->context);
+ ret = _krb5_plugin_run_f(r->context, &gss_preauth_authorizer_data,
+ 0, &ctx, pa_gss_authorize_cb);
+
+ if (ret != KRB5_PLUGIN_NO_HANDLE) {
+ const char *msg = krb5_get_error_message(r->context, ret);
+
+ kdc_log(r->context, r->config, 7,
+ "GSS authz plugin %sauthorize%s %s initiator %.*s: %s",
+ ctx.authorized ? "" : "did not " ,
+ ctx.authorized ? "d" : "",
+ gss_oid_to_name(gcp->mech_type),
+ (int)display_name->length, (char *)display_name->value,
+ msg);
+ krb5_free_error_message(r->context, msg);
+ }
+
+ *authorized = ctx.authorized;
+ *initiator_princ = ctx.initiator_princ;
+
+ return ret;
+}
+
+static krb5_error_code
+pa_gss_authorize_default(astgs_request_t r,
+ struct gss_client_params *gcp,
+ gss_const_buffer_t display_name,
+ krb5_boolean *authorized,
+ krb5_principal *initiator_princ)
+{
+ krb5_error_code ret;
+ krb5_principal principal;
+ krb5_const_realm realm = r->server->principal->realm;
+ int flags = 0, cross_realm_allowed = 0, unauth_anon;
+
+ /*
+ * gss_cross_realm_mechanisms_allowed is a list of GSS-API mechanisms
+ * that are allowed to map directly to Kerberos principals in any
+ * realm. If the authenticating mechanism is not on the list, then
+ * the initiator will be mapped to an enterprise principal in the
+ * service realm. This is useful to stop synthetic principals in
+ * foreign realms being conflated with true cross-realm principals.
+ */
+ if (r->config->gss_cross_realm_mechanisms_allowed) {
+ OM_uint32 minor;
+
+ gss_test_oid_set_member(&minor, gcp->mech_type,
+ r->config->gss_cross_realm_mechanisms_allowed,
+ &cross_realm_allowed);
+ }
+
+ kdc_log(r->context, r->config, 10,
+ "Initiator %.*s will be mapped to %s",
+ (int)display_name->length, (char *)display_name->value,
+ cross_realm_allowed ? "nt-principal" : "nt-enterprise-principal");
+
+ if (!cross_realm_allowed)
+ flags |= KRB5_PRINCIPAL_PARSE_ENTERPRISE | KRB5_PRINCIPAL_PARSE_NO_REALM;
+
+ ret = _krb5_gss_pa_parse_name(r->context, gcp->initiator_name,
+ flags, &principal);
+ if (ret) {
+ const char *msg = krb5_get_error_message(r->context, ret);
+
+ kdc_log(r->context, r->config, 2,
+ "Failed to parse %s initiator name %.*s: %s",
+ gss_oid_to_name(gcp->mech_type),
+ (int)display_name->length, (char *)display_name->value, msg);
+ krb5_free_error_message(r->context, msg);
+
+ return ret;
+ }
+
+ /*
+ * GSS_C_ANON_FLAG indicates the client requested anonymous authentication
+ * (it is validated against the request-anonymous flag).
+ *
+ * _kdc_is_anonymous_pkinit() returns TRUE if the principal contains both
+ * the well known anonymous name and realm.
+ */
+ unauth_anon = (gcp->flags & GSS_C_ANON_FLAG) &&
+ _kdc_is_anonymous_pkinit(r->context, principal);
+
+ /*
+ * Always use the anonymous entry created in our HDB, i.e. with the local
+ * realm, for authorizing anonymous requests. This matches PKINIT behavior
+ * as anonymous PKINIT requests include the KDC realm in the request.
+ */
+ if (unauth_anon || (flags & KRB5_PRINCIPAL_PARSE_ENTERPRISE)) {
+ ret = krb5_principal_set_realm(r->context, principal, realm);
+ if (ret) {
+ krb5_free_principal(r->context, principal);
+ return ret;
+ }
+ }
+
+ if (unauth_anon) {
+ /*
+ * Special case to avoid changing _kdc_as_rep(). If the initiator is
+ * the unauthenticated anonymous principal, r->client_princ also needs
+ * to be set in order to force the AS-REP realm to be set to the well-
+ * known anonymous identity. This is because (unlike anonymous PKINIT)
+ * we only require the anonymous flag, not the anonymous name, in the
+ * client AS-REQ.
+ */
+ krb5_principal anon_princ;
+
+ ret = krb5_copy_principal(r->context, principal, &anon_princ);
+ if (ret)
+ return ret;
+
+ krb5_free_principal(r->context, r->client_princ);
+ r->client_princ = anon_princ;
+ }
+
+ *authorized = TRUE;
+ *initiator_princ = principal;
+
+ return 0;
+}
+
+krb5_error_code
+_kdc_gss_check_client(astgs_request_t r,
+ gss_client_params *gcp,
+ char **client_name)
+{
+ krb5_error_code ret;
+ krb5_principal initiator_princ = NULL;
+ hdb_entry *initiator = NULL;
+ krb5_boolean authorized = FALSE;
+ HDB *clientdb = r->clientdb;
+
+ OM_uint32 minor;
+ gss_buffer_desc display_name = GSS_C_EMPTY_BUFFER;
+ gss_const_buffer_t display_name_p;
+
+ *client_name = NULL;
+
+ pa_gss_display_name(gcp->initiator_name, &display_name, &display_name_p);
+
+ /*
+ * If no plugins handled the authorization request, then all clients
+ * are authorized as the directly corresponding Kerberos principal.
+ */
+ ret = pa_gss_authorize_plugin(r, gcp, display_name_p,
+ &authorized, &initiator_princ);
+ if (ret == KRB5_PLUGIN_NO_HANDLE)
+ ret = pa_gss_authorize_default(r, gcp, display_name_p,
+ &authorized, &initiator_princ);
+ if (ret == 0 && !authorized)
+ ret = KRB5_KDC_ERR_CLIENT_NAME_MISMATCH;
+ if (ret)
+ goto out;
+
+ ret = krb5_unparse_name(r->context, initiator_princ, client_name);
+ if (ret)
+ goto out;
+
+ kdc_log(r->context, r->config, 4,
+ "Mapped GSS %s initiator %.*s to principal %s",
+ gss_oid_to_name(gcp->mech_type),
+ (int)display_name_p->length, (char *)display_name_p->value,
+ *client_name);
+
+ ret = _kdc_db_fetch(r->context,
+ r->config,
+ initiator_princ,
+ HDB_F_FOR_AS_REQ | HDB_F_GET_CLIENT |
+ HDB_F_CANON | HDB_F_SYNTHETIC_OK,
+ NULL,
+ &r->clientdb,
+ &initiator);
+ if (ret) {
+ const char *msg = krb5_get_error_message(r->context, ret);
+
+ kdc_log(r->context, r->config, 4, "UNKNOWN -- %s: %s",
+ *client_name, msg);
+ krb5_free_error_message(r->context, msg);
+
+ goto out;
+ }
+
+ /*
+ * If the AS-REQ client name was the well-known federated name, then
+ * replace the client name with the initiator name. Otherwise, the
+ * two principals must match, noting that GSS pre-authentication is
+ * for authentication, not general purpose impersonation.
+ */
+ if (krb5_principal_is_federated(r->context, r->client->principal)) {
+ initiator->flags.force_canonicalize = 1;
+
+ _kdc_free_ent(r->context, clientdb, r->client);
+ r->client = initiator;
+ initiator = NULL;
+ } else if (!krb5_principal_compare(r->context,
+ r->client->principal,
+ initiator->principal)) {
+ kdc_log(r->context, r->config, 2,
+ "GSS %s initiator %.*s does not match principal %s",
+ gss_oid_to_name(gcp->mech_type),
+ (int)display_name_p->length, (char *)display_name_p->value,
+ r->cname);
+ ret = KRB5_KDC_ERR_CLIENT_NAME_MISMATCH;
+ goto out;
+ }
+
+out:
+ krb5_free_principal(r->context, initiator_princ);
+ if (initiator)
+ _kdc_free_ent(r->context, r->clientdb, initiator);
+ gss_release_buffer(&minor, &display_name);
+
+ return ret;
+}
+
+krb5_error_code
+_kdc_gss_mk_pa_reply(astgs_request_t r,
+ gss_client_params *gcp)
+{
+ krb5_error_code ret;
+ const KDC_REQ *req = &r->req;
+
+ if (gcp->major == GSS_S_COMPLETE) {
+ krb5_enctype enctype;
+ uint32_t kfe = 0;
+ krb5_keyblock *reply_key = NULL;
+
+ if (krb5_principal_is_krbtgt(r->context, r->server_princ))
+ kfe |= KFE_IS_TGS;
+
+ ret = _kdc_find_etype(r, kfe, req->req_body.etype.val,
+ req->req_body.etype.len, &enctype, NULL, NULL);
+ if (ret)
+ return ret;
+
+ ret = _krb5_gss_pa_derive_key(r->context, gcp->context_handle,
+ req->req_body.nonce,
+ enctype, &reply_key);
+ if (ret) {
+ kdc_log(r->context, r->config, 10,
+ "Failed to derive GSS reply key: %d", ret);
+ return ret;
+ }
+
+ krb5_free_keyblock_contents(r->context, &r->reply_key);
+ r->reply_key = *reply_key;
+ free(reply_key);
+ } else if (gcp->major == GSS_S_CONTINUE_NEEDED) {
+ ret = pa_gss_set_context_state(r, gcp);
+ if (ret)
+ return ret;
+ }
+
+ /* only return padata in error case if we have an error token */
+ if (!GSS_ERROR(gcp->major) || gcp->output_token.length) {
+ ret = krb5_padata_add(r->context, r->rep.padata, KRB5_PADATA_GSS,
+ gcp->output_token.value, gcp->output_token.length);
+ if (ret)
+ return ret;
+
+ /* token is now owned by r->rep.padata */
+ gcp->output_token.length = 0;
+ gcp->output_token.value = NULL;
+ }
+
+ if (gcp->major == GSS_S_CONTINUE_NEEDED)
+ ret = KRB5_KDC_ERR_MORE_PREAUTH_DATA_REQUIRED;
+ else
+ ret = _krb5_gss_map_error(gcp->major, gcp->minor);
+
+ return ret;
+}
+
+krb5_error_code
+_kdc_gss_mk_composite_name_ad(astgs_request_t r,
+ gss_client_params *gcp)
+{
+ krb5_error_code ret;
+ krb5_data data;
+
+ OM_uint32 major, minor;
+ gss_buffer_desc namebuf = GSS_C_EMPTY_BUFFER;
+
+ if (!r->config->enable_gss_auth_data || (gcp->flags & GSS_C_ANON_FLAG))
+ return 0;
+
+ major = gss_export_name_composite(&minor, gcp->initiator_name, &namebuf);
+ if (major == GSS_S_COMPLETE) {
+ _krb5_gss_buffer_to_data(&namebuf, &data);
+
+ ret = _kdc_tkt_add_if_relevant_ad(r->context, &r->et,
+ KRB5_AUTHDATA_GSS_COMPOSITE_NAME,
+ &data);
+ } else if (major != GSS_S_UNAVAILABLE)
+ ret = _krb5_gss_map_error(major, minor);
+ else
+ ret = 0;
+
+ gss_release_buffer(&minor, &namebuf);
+
+ return ret;
+}
+
+static void HEIM_CALLCONV
+pa_gss_dealloc_client_params(void *ptr)
+{
+ gss_client_params *gcp = ptr;
+ OM_uint32 minor;
+
+ if (gcp == NULL)
+ return;
+
+ gss_delete_sec_context(&minor, &gcp->context_handle, GSS_C_NO_BUFFER);
+ gss_release_name(&minor, &gcp->initiator_name);
+ gss_release_buffer(&minor, &gcp->output_token);
+ free_Checksum(&gcp->req_body_checksum);
+ memset(gcp, 0, sizeof(*gcp));
+}
+
+krb5_error_code
+_kdc_gss_get_mechanism_config(krb5_context context,
+ const char *section,
+ const char *key,
+ gss_OID_set *oidsp)
+{
+ krb5_error_code ret;
+ char **mechs, **mechp;
+
+ gss_OID_set oids = GSS_C_NO_OID_SET;
+ OM_uint32 major, minor;
+
+ mechs = krb5_config_get_strings(context, NULL, section, key, NULL);
+ if (mechs == NULL)
+ return 0;
+
+ major = gss_create_empty_oid_set(&minor, &oids);
+ if (GSS_ERROR(major)) {
+ krb5_config_free_strings(mechs);
+ return _krb5_gss_map_error(major, minor);
+ }
+
+ for (mechp = mechs; *mechp; mechp++) {
+ gss_OID oid = gss_name_to_oid(*mechp);
+ if (oid == GSS_C_NO_OID)
+ continue;
+
+ major = gss_add_oid_set_member(&minor, oid, &oids);
+ if (GSS_ERROR(major))
+ break;
+ }
+
+ ret = _krb5_gss_map_error(major, minor);
+ if (ret == 0)
+ *oidsp = oids;
+ else
+ gss_release_oid_set(&minor, &oids);
+
+ krb5_config_free_strings(mechs);
+
+ return ret;
+}
+
+static void
+pa_gss_display_status(astgs_request_t r,
+ OM_uint32 major,
+ OM_uint32 minor,
+ gss_client_params *gcp,
+ const char *msg)
+{
+ krb5_error_code ret = _krb5_gss_map_error(major, minor);
+ gss_buffer_desc buf = GSS_C_EMPTY_BUFFER;
+ OM_uint32 dmaj, dmin;
+ OM_uint32 more = 0;
+ char *gmmsg = NULL;
+ char *gmsg = NULL;
+ char *s = NULL;
+
+ do {
+ gss_release_buffer(&dmin, &buf);
+ dmaj = gss_display_status(&dmin, major, GSS_C_GSS_CODE, GSS_C_NO_OID,
+ &more, &buf);
+ if (GSS_ERROR(dmaj) ||
+ buf.length >= INT_MAX ||
+ asprintf(&s, "%s%s%.*s", gmsg ? gmsg : "", gmsg ? ": " : "",
+ (int)buf.length, (char *)buf.value) == -1 ||
+ s == NULL) {
+ free(gmsg);
+ gmsg = NULL;
+ break;
+ }
+ gmsg = s;
+ s = NULL;
+ } while (!GSS_ERROR(dmaj) && more);
+
+ if (gcp->mech_type != GSS_C_NO_OID) {
+ do {
+ gss_release_buffer(&dmin, &buf);
+ dmaj = gss_display_status(&dmin, major, GSS_C_MECH_CODE,
+ gcp->mech_type, &more, &buf);
+ if (GSS_ERROR(dmaj) ||
+ asprintf(&s, "%s%s%.*s", gmmsg ? gmmsg : "", gmmsg ? ": " : "",
+ (int)buf.length, (char *)buf.value) == -1 ||
+ s == NULL) {
+ free(gmmsg);
+ gmmsg = NULL;
+ break;
+ }
+ gmmsg = s;
+ s = NULL;
+ } while (!GSS_ERROR(dmaj) && more);
+ }
+
+ if (gmsg == NULL)
+ krb5_set_error_message(r->context, ENOMEM,
+ "Error displaying GSS-API status");
+ else
+ krb5_set_error_message(r->context, ret, "%s%s%s%s", gmsg,
+ gmmsg ? " (" : "", gmmsg ? gmmsg : "",
+ gmmsg ? ")" : "");
+ krb5_prepend_error_message(r->context, ret, "%s", msg);
+
+ kdc_log(r->context, r->config, 1,
+ "%s: %s%s%s%s",
+ msg, gmsg, gmmsg ? " (" : "", gmmsg ? gmmsg : "",
+ gmmsg ? ")" : "");
+
+ free(gmmsg);
+ free(gmsg);
+}
+
+static const gss_buffer_desc
+gss_pa_unknown_display_name = {
+ sizeof("<unknown name>") - 1,
+ "<unknown name>"
+};
+
+static void
+pa_gss_display_name(gss_name_t name,
+ gss_buffer_t namebuf,
+ gss_const_buffer_t *namebuf_p)
+{
+ OM_uint32 major, minor;
+
+ major = gss_display_name(&minor, name, namebuf, NULL);
+ if (GSS_ERROR(major))
+ *namebuf_p = &gss_pa_unknown_display_name;
+ else
+ *namebuf_p = namebuf;
+}
+
+static krb5_error_code KRB5_LIB_CALL
+pa_gss_finalize_pac_cb(krb5_context context,
+ const void *plug,
+ void *plugctx,
+ void *userctx)
+{
+ const krb5plugin_gss_preauth_authorizer_ftable *authorizer = plug;
+
+ return authorizer->finalize_pac(plugctx, userctx);
+}
+
+
+krb5_error_code
+_kdc_gss_finalize_pac(astgs_request_t r,
+ gss_client_params *gcp)
+{
+ krb5_error_code ret;
+
+ krb5_clear_error_message(r->context);
+ ret = _krb5_plugin_run_f(r->context, &gss_preauth_authorizer_data,
+ 0, r, pa_gss_finalize_pac_cb);
+
+ if (ret == KRB5_PLUGIN_NO_HANDLE)
+ ret = 0;
+
+ return ret;
+}
diff --git a/third_party/heimdal/kdc/gss_preauth_authorizer_plugin.h b/third_party/heimdal/kdc/gss_preauth_authorizer_plugin.h
new file mode 100644
index 0000000..1bc1c91
--- /dev/null
+++ b/third_party/heimdal/kdc/gss_preauth_authorizer_plugin.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2019 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef HEIMDAL_KDC_GSS_PREAUTH_AUTHORIZER_PLUGIN_H
+#define HEIMDAL_KDC_GSS_PREAUTH_AUTHORIZER_PLUGIN_H 1
+
+#define KDC_GSS_PREAUTH_AUTHORIZER "kdc_gss_preauth_authorizer"
+#define KDC_GSS_PREAUTH_AUTHORIZER_VERSION_1 1
+
+#include <krb5.h>
+#include <gssapi/gssapi.h>
+
+/*
+ * @param init Plugin initialization function (see krb5-plugin(7))
+ * @param minor_version The plugin minor version number (1)
+ * @param fini Plugin finalization function
+ * @param authorize Plugin name authorization function
+ *
+ * -# plug_ctx, the context value output by the plugin's init function
+ * -# context, a krb5_context
+ * -# req, the AS-REQ request
+ * -# client_name, the requested client principal name
+ * -# client, the requested client HDB entry
+ * -# initiator_name, the authenticated GSS initiator name
+ * -# ret_flags, the flags returned by GSS_Init_sec_context()
+ * -# authorized, indicate whether the initiator was authorized
+ * -# mapped_name, the mapped principal name
+ *
+ * @ingroup krb5_support
+ */
+
+typedef struct krb5plugin_gss_preauth_authorizer_ftable_desc {
+ HEIM_PLUGIN_FTABLE_COMMON_ELEMENTS(krb5_context);
+ krb5_error_code (KRB5_LIB_CALL *authorize)(void *, /*plug_ctx*/
+ astgs_request_t, /*r*/
+ gss_const_name_t, /*initiator_name*/
+ gss_const_OID, /*mech_type*/
+ OM_uint32, /*ret_flags*/
+ krb5_boolean *, /*authorized*/
+ krb5_principal *); /*mapped_name*/
+ krb5_error_code (KRB5_LIB_CALL *finalize_pac)(void *, /*plug_ctx*/
+ astgs_request_t); /*r*/
+} krb5plugin_gss_preauth_authorizer_ftable;
+
+#endif /* HEIMDAL_KDC_GSS_PREAUTH_AUTHORIZER_PLUGIN_H */
diff --git a/third_party/heimdal/kdc/headers.h b/third_party/heimdal/kdc/headers.h
new file mode 100644
index 0000000..ffe49c8
--- /dev/null
+++ b/third_party/heimdal/kdc/headers.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 1997 - 2006 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * $Id$
+ */
+
+#ifndef __HEADERS_H__
+#define __HEADERS_H__
+
+#include <config.h>
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdarg.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#ifdef HAVE_NETINET_IN6_H
+#include <netinet/in6.h>
+#endif
+#ifdef HAVE_NETINET6_IN6_H
+#include <netinet6/in6.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+#ifdef HAVE_UTIL_H
+#include <util.h>
+#endif
+#ifdef HAVE_LIBUTIL_H
+#include <libutil.h>
+#endif
+#include <err.h>
+#include <roken.h>
+#include <getarg.h>
+#include <base64.h>
+#include <parse_units.h>
+#include <krb5.h>
+#include <krb5_locl.h>
+#include <digest_asn1.h>
+#include <kx509_asn1.h>
+#include <hdb.h>
+#include <hdb_err.h>
+#include <der.h>
+#include <gssapi/gssapi.h>
+
+#ifndef NO_NTLM
+#include <heimntlm.h>
+#endif
+#include <kdc.h>
+#include <kdc-plugin.h>
+#include <kdc-audit.h>
+
+#include <heimbase.h>
+
+#undef ALLOC
+#define ALLOC(X) ((X) = calloc(1, sizeof(*(X))))
+#undef ALLOC_SEQ
+#define ALLOC_SEQ(X, N) do { (X)->len = (N); \
+(X)->val = calloc((X)->len, sizeof(*(X)->val)); } while(0)
+
+#endif /* __HEADERS_H__ */
diff --git a/third_party/heimdal/kdc/hprop-version.rc b/third_party/heimdal/kdc/hprop-version.rc
new file mode 100644
index 0000000..1e782f5
--- /dev/null
+++ b/third_party/heimdal/kdc/hprop-version.rc
@@ -0,0 +1,36 @@
+/***********************************************************************
+ * Copyright (c) 2010, Secure Endpoints Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ **********************************************************************/
+
+#define RC_FILE_TYPE VFT_APP
+#define RC_FILE_DESC_0409 "KDC Database Propagation Tool"
+#define RC_FILE_ORIG_0409 "hprop.exe"
+
+#include "../windows/version.rc"
diff --git a/third_party/heimdal/kdc/hprop.8 b/third_party/heimdal/kdc/hprop.8
new file mode 100644
index 0000000..acf66bc
--- /dev/null
+++ b/third_party/heimdal/kdc/hprop.8
@@ -0,0 +1,130 @@
+.\" Copyright (c) 2000 - 2004 Kungliga Tekniska Högskolan
+.\" (Royal Institute of Technology, Stockholm, Sweden).
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\"
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\"
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" 3. Neither the name of the Institute nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $Id$
+.\"
+.Dd December 8, 2004
+.Dt HPROP 8
+.Os HEIMDAL
+.Sh NAME
+.Nm hprop
+.Nd propagate the KDC database
+.Sh SYNOPSIS
+.Nm
+.Bk -words
+.Oo Fl m Ar file \*(Ba Xo
+.Fl Fl master-key= Ns Pa file
+.Xc
+.Oc
+.Oo Fl d Ar file \*(Ba Xo
+.Fl Fl database= Ns Pa file
+.Xc
+.Oc
+.Op Fl Fl source= Ns Ar heimdal|mit-dump
+.Oo Fl r Ar string \*(Ba Xo
+.Xc
+.Oc
+.Oo Fl c Ar cell \*(Ba Xo
+.Fl Fl cell= Ns Ar cell
+.Xc
+.Oc
+.Oo Fl k Ar keytab \*(Ba Xo
+.Fl Fl keytab= Ns Ar keytab
+.Xc
+.Oc
+.Oo Fl R Ar string \*(Ba Xo
+.Fl Fl v5-realm= Ns Ar string
+.Xc
+.Oc
+.Op Fl D | Fl Fl decrypt
+.Op Fl E | Fl Fl encrypt
+.Op Fl n | Fl Fl stdout
+.Op Fl v | Fl Fl verbose
+.Op Fl Fl version
+.Op Fl h | Fl Fl help
+.Op Ar host Ns Op : Ns Ar port
+.Ar ...
+.Ek
+.Sh DESCRIPTION
+.Nm
+takes a principal database in a specified format and converts it into
+a stream of Heimdal database records. This stream can either be
+written to standard out, or (more commonly) be propagated to a
+.Xr hpropd 8
+server running on a different machine.
+.Pp
+If propagating, it connects to all
+.Ar hosts
+specified on the command by opening a TCP connection to port 754
+(service hprop) and sends the database in encrypted form.
+.Pp
+Supported options:
+.Bl -tag -width Ds
+.It Fl m Ar file , Fl Fl master-key= Ns Pa file
+Where to find the master key to encrypt or decrypt keys with.
+.It Fl d Ar file , Fl Fl database= Ns Pa file
+The database to be propagated.
+.It Fl Fl source= Ns Ar heimdal|mit-dump|krb4-dump|kaserver
+Specifies the type of the source database. Alternatives include:
+.Pp
+.Bl -tag -width mit-dump -compact -offset indent
+.It heimdal
+a Heimdal database
+.It mit-dump
+a MIT Kerberos 5 dump file
+.El
++.It Fl k Ar keytab , Fl Fl keytab= Ns Ar keytab
+The keytab to use for fetching the key to be used for authenticating
+to the propagation daemon(s). The key
+.Pa hprop/hostname
+is used from this keytab. The default is to fetch the key from the
+KDC database.
+.It Fl R Ar string , Fl Fl v5-realm= Ns Ar string
+Local realm override.
+.It Fl D , Fl Fl decrypt
+The encryption keys in the database can either be in clear, or
+encrypted with a master key. This option transmits the database with
+unencrypted keys.
+.It Fl E , Fl Fl encrypt
+This option transmits the database with encrypted keys. This is the
+default if no option is supplied.
+.It Fl n , Fl Fl stdout
+Dump the database on stdout, in a format that can be fed to hpropd.
+.El
+.Sh EXAMPLES
+The following will propagate a database to another machine (which
+should run
+.Xr hpropd 8 ) :
+.Bd -literal -offset indent
+$ hprop slave-1 slave-2
+.Ed
+.Sh SEE ALSO
+.Xr hpropd 8
diff --git a/third_party/heimdal/kdc/hprop.c b/third_party/heimdal/kdc/hprop.c
new file mode 100644
index 0000000..c1db11b
--- /dev/null
+++ b/third_party/heimdal/kdc/hprop.c
@@ -0,0 +1,482 @@
+/*
+ * Copyright (c) 1997 - 2005 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#define KRB5_DEPRECATED /* uses v4 functions that will die */
+
+#include "hprop.h"
+
+static int version_flag;
+static int help_flag;
+static const char *ktname = HPROP_KEYTAB;
+static const char *database;
+static char *mkeyfile;
+static int to_stdout;
+static int verbose_flag;
+static int encrypt_flag;
+static int decrypt_flag;
+static hdb_master_key mkey5;
+
+static char *source_type;
+
+static char *local_realm=NULL;
+
+static int
+open_socket(krb5_context context, const char *hostname, const char *port)
+{
+ struct addrinfo *ai, *a;
+ struct addrinfo hints;
+ int error;
+
+ memset (&hints, 0, sizeof(hints));
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ error = getaddrinfo (hostname, port, &hints, &ai);
+ if (error) {
+ warnx ("%s: %s", hostname, gai_strerror(error));
+ return -1;
+ }
+
+ for (a = ai; a != NULL; a = a->ai_next) {
+ int s;
+
+ s = socket (a->ai_family, a->ai_socktype, a->ai_protocol);
+ if (s < 0)
+ continue;
+ if (connect (s, a->ai_addr, a->ai_addrlen) < 0) {
+ warn ("connect(%s)", hostname);
+ close (s);
+ continue;
+ }
+ freeaddrinfo (ai);
+ return s;
+ }
+ warnx ("failed to contact %s", hostname);
+ freeaddrinfo (ai);
+ return -1;
+}
+
+krb5_error_code
+v5_prop(krb5_context context, HDB *db, hdb_entry *entry, void *appdata)
+{
+ krb5_error_code ret;
+ struct prop_data *pd = appdata;
+ krb5_data data;
+
+ if(encrypt_flag) {
+ ret = hdb_seal_keys_mkey(context, entry, mkey5);
+ if (ret) {
+ krb5_warn(context, ret, "hdb_seal_keys_mkey");
+ return ret;
+ }
+ }
+ if(decrypt_flag) {
+ ret = hdb_unseal_keys_mkey(context, entry, mkey5);
+ if (ret) {
+ krb5_warn(context, ret, "hdb_unseal_keys_mkey");
+ return ret;
+ }
+ }
+
+ ret = hdb_entry2value(context, entry, &data);
+ if(ret) {
+ krb5_warn(context, ret, "hdb_entry2value");
+ return ret;
+ }
+
+ if(to_stdout)
+ ret = krb5_write_message(context, &pd->sock, &data);
+ else
+ ret = krb5_write_priv_message(context, pd->auth_context,
+ &pd->sock, &data);
+ krb5_data_free(&data);
+ return ret;
+}
+
+struct getargs args[] = {
+ { "master-key", 'm', arg_string, &mkeyfile, "v5 master key file", "file" },
+ { "database", 'd', arg_string, rk_UNCONST(&database), "database", "file" },
+ { "source", 0, arg_string, &source_type, "type of database to read",
+ "heimdal"
+ "|mit-dump"
+ },
+
+ { "keytab", 'k', arg_string, rk_UNCONST(&ktname),
+ "keytab to use for authentication", "keytab" },
+ { "v5-realm", 'R', arg_string, &local_realm, "v5 realm to use", NULL },
+ { "decrypt", 'D', arg_flag, &decrypt_flag, "decrypt keys", NULL },
+ { "encrypt", 'E', arg_flag, &encrypt_flag, "encrypt keys", NULL },
+ { "stdout", 'n', arg_flag, &to_stdout, "dump to stdout", NULL },
+ { "verbose", 'v', arg_flag, &verbose_flag, NULL, NULL },
+ { "version", 0, arg_flag, &version_flag, NULL, NULL },
+ { "help", 'h', arg_flag, &help_flag, NULL, NULL }
+};
+
+static int num_args = sizeof(args) / sizeof(args[0]);
+
+static void
+usage(int ret)
+{
+ arg_printusage (args, num_args, NULL, "[host[:port]] ...");
+ exit (ret);
+}
+
+static void
+get_creds(krb5_context context, krb5_ccache *cache)
+{
+ krb5_keytab keytab;
+ krb5_principal client;
+ krb5_error_code ret;
+ krb5_get_init_creds_opt *init_opts;
+ krb5_preauthtype preauth = KRB5_PADATA_ENC_TIMESTAMP;
+ krb5_creds creds;
+
+ ret = krb5_kt_register(context, &hdb_get_kt_ops);
+ if(ret) krb5_err(context, 1, ret, "krb5_kt_register");
+
+ ret = krb5_kt_resolve(context, ktname, &keytab);
+ if(ret) krb5_err(context, 1, ret, "krb5_kt_resolve");
+
+ ret = krb5_make_principal(context, &client, NULL,
+ "kadmin", HPROP_NAME, NULL);
+ if(ret) krb5_err(context, 1, ret, "krb5_make_principal");
+
+ ret = krb5_get_init_creds_opt_alloc(context, &init_opts);
+ if(ret) krb5_err(context, 1, ret, "krb5_get_init_creds_opt_alloc");
+ krb5_get_init_creds_opt_set_preauth_list(init_opts, &preauth, 1);
+
+ ret = krb5_get_init_creds_keytab(context, &creds, client, keytab, 0, NULL, init_opts);
+ if(ret) krb5_err(context, 1, ret, "krb5_get_init_creds");
+
+ krb5_get_init_creds_opt_free(context, init_opts);
+
+ ret = krb5_kt_close(context, keytab);
+ if(ret) krb5_err(context, 1, ret, "krb5_kt_close");
+
+ ret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, cache);
+ if(ret) krb5_err(context, 1, ret, "krb5_cc_new_unique");
+
+ ret = krb5_cc_initialize(context, *cache, client);
+ if(ret) krb5_err(context, 1, ret, "krb5_cc_initialize");
+
+ krb5_free_principal(context, client);
+
+ ret = krb5_cc_store_cred(context, *cache, &creds);
+ if(ret) krb5_err(context, 1, ret, "krb5_cc_store_cred");
+
+ krb5_free_cred_contents(context, &creds);
+}
+
+enum hprop_source {
+ HPROP_HEIMDAL = 1,
+ HPROP_MIT_DUMP
+};
+
+struct {
+ int type;
+ const char *name;
+} types[] = {
+ { HPROP_HEIMDAL, "heimdal" },
+ { HPROP_MIT_DUMP, "mit-dump" }
+};
+
+static int
+parse_source_type(const char *s)
+{
+ size_t i;
+ for(i = 0; i < sizeof(types) / sizeof(types[0]); i++) {
+ if(strstr(types[i].name, s) == types[i].name)
+ return types[i].type;
+ }
+ return 0;
+}
+
+static int
+iterate (krb5_context context,
+ const char *database_name,
+ HDB *db,
+ int type,
+ struct prop_data *pd)
+{
+ int ret;
+
+ switch(type) {
+ case HPROP_MIT_DUMP:
+ ret = mit_prop_dump(pd, database_name);
+ if (ret)
+ krb5_warn(context, ret, "mit_prop_dump");
+ break;
+ case HPROP_HEIMDAL:
+ ret = hdb_foreach(context, db, HDB_F_DECRYPT, v5_prop, pd);
+ if(ret)
+ krb5_warn(context, ret, "hdb_foreach");
+ break;
+ default:
+ krb5_errx(context, 1, "unknown prop type: %d", type);
+ }
+ return ret;
+}
+
+static int
+dump_database (krb5_context context, int type,
+ const char *database_name, HDB *db)
+{
+ krb5_error_code ret;
+ struct prop_data pd;
+ krb5_data data;
+
+ pd.context = context;
+ pd.auth_context = NULL;
+ pd.sock = STDOUT_FILENO;
+
+ ret = iterate (context, database_name, db, type, &pd);
+ if (ret)
+ krb5_errx(context, 1, "iterate failure");
+ krb5_data_zero (&data);
+ ret = krb5_write_message (context, &pd.sock, &data);
+ if (ret)
+ krb5_err(context, 1, ret, "krb5_write_message");
+
+ return 0;
+}
+
+static int
+propagate_database (krb5_context context, int type,
+ const char *database_name,
+ HDB *db, krb5_ccache ccache,
+ int optidx, int argc, char **argv)
+{
+ krb5_principal server;
+ krb5_error_code ret;
+ int i, failed = 0;
+
+ for(i = optidx; i < argc; i++){
+ krb5_auth_context auth_context;
+ int fd;
+ struct prop_data pd;
+ krb5_data data;
+
+ char *port, portstr[NI_MAXSERV];
+ char *host = argv[i];
+
+ port = strchr(host, ':');
+ if(port == NULL) {
+ snprintf(portstr, sizeof(portstr), "%u",
+ ntohs(krb5_getportbyname (context, "hprop", "tcp",
+ HPROP_PORT)));
+ port = portstr;
+ } else
+ *port++ = '\0';
+
+ fd = open_socket(context, host, port);
+ if(fd < 0) {
+ failed++;
+ krb5_warn (context, errno, "connect %s", host);
+ continue;
+ }
+
+ ret = krb5_sname_to_principal(context, argv[i],
+ HPROP_NAME, KRB5_NT_SRV_HST, &server);
+ if(ret) {
+ failed++;
+ krb5_warn(context, ret, "krb5_sname_to_principal(%s)", host);
+ close(fd);
+ continue;
+ }
+
+ if (local_realm) {
+ krb5_realm my_realm;
+ ret = krb5_get_default_realm(context,&my_realm);
+ if (ret == 0) {
+ ret = krb5_principal_set_realm(context,server,my_realm);
+ krb5_xfree(my_realm);
+ }
+ if (ret) {
+ failed++;
+ krb5_warn(context, ret, "unable to obtain default or set realm");
+ krb5_free_principal(context, server);
+ close(fd);
+ continue;
+ }
+ }
+
+ auth_context = NULL;
+ ret = krb5_sendauth(context,
+ &auth_context,
+ &fd,
+ HPROP_VERSION,
+ NULL,
+ server,
+ AP_OPTS_MUTUAL_REQUIRED | AP_OPTS_USE_SUBKEY,
+ NULL, /* in_data */
+ NULL, /* in_creds */
+ ccache,
+ NULL,
+ NULL,
+ NULL);
+
+ krb5_free_principal(context, server);
+
+ if(ret) {
+ failed++;
+ krb5_warn(context, ret, "krb5_sendauth (%s)", host);
+ goto next_host;
+ }
+
+ pd.context = context;
+ pd.auth_context = auth_context;
+ pd.sock = fd;
+
+ ret = iterate (context, database_name, db, type, &pd);
+ if (ret) {
+ krb5_warnx(context, "iterate to host %s failed", host);
+ failed++;
+ goto next_host;
+ }
+
+ krb5_data_zero (&data);
+ ret = krb5_write_priv_message(context, auth_context, &fd, &data);
+ if(ret) {
+ krb5_warn(context, ret, "krb5_write_priv_message");
+ failed++;
+ goto next_host;
+ }
+
+ ret = krb5_read_priv_message(context, auth_context, &fd, &data);
+ if(ret) {
+ krb5_warn(context, ret, "krb5_read_priv_message: %s", host);
+ failed++;
+ goto next_host;
+ } else
+ krb5_data_free (&data);
+
+ next_host:
+ krb5_auth_con_free(context, auth_context);
+ close(fd);
+ }
+ if (failed)
+ return 1;
+ return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+ krb5_error_code ret;
+ krb5_context context;
+ krb5_ccache ccache = NULL;
+ HDB *db = NULL;
+ int optidx = 0;
+
+ int type, exit_code;
+
+ setprogname(argv[0]);
+
+ if(getarg(args, num_args, argc, argv, &optidx))
+ usage(1);
+
+ if(help_flag)
+ usage(0);
+
+ if(version_flag){
+ print_version(NULL);
+ exit(0);
+ }
+
+ ret = krb5_init_context(&context);
+ if(ret)
+ exit(1);
+
+ /* We may be reading an old database encrypted with a DES master key. */
+ ret = krb5_allow_weak_crypto(context, 1);
+ if(ret)
+ krb5_err(context, 1, ret, "krb5_allow_weak_crypto");
+
+ if(local_realm)
+ krb5_set_default_realm(context, local_realm);
+
+ if(encrypt_flag && decrypt_flag)
+ krb5_errx(context, 1,
+ "only one of `--encrypt' and `--decrypt' is meaningful");
+
+ if(source_type != NULL) {
+ type = parse_source_type(source_type);
+ if(type == 0)
+ krb5_errx(context, 1, "unknown source type `%s'", source_type);
+ } else
+ type = HPROP_HEIMDAL;
+
+ if(!to_stdout)
+ get_creds(context, &ccache);
+
+ if(decrypt_flag || encrypt_flag) {
+ ret = hdb_read_master_key(context, mkeyfile, &mkey5);
+ if(ret && ret != ENOENT)
+ krb5_err(context, 1, ret, "hdb_read_master_key");
+ if(ret)
+ krb5_errx(context, 1, "No master key file found");
+ }
+
+ switch(type) {
+ case HPROP_MIT_DUMP:
+ if (database == NULL)
+ krb5_errx(context, 1, "no dump file specified");
+ break;
+ case HPROP_HEIMDAL:
+ ret = hdb_create (context, &db, database);
+ if(ret)
+ krb5_err(context, 1, ret, "hdb_create: %s", database);
+ ret = db->hdb_open(context, db, O_RDONLY, 0);
+ if(ret)
+ krb5_err(context, 1, ret, "db->hdb_open");
+ break;
+ default:
+ krb5_errx(context, 1, "unknown dump type `%d'", type);
+ break;
+ }
+
+ if (to_stdout)
+ exit_code = dump_database (context, type, database, db);
+ else
+ exit_code = propagate_database (context, type, database,
+ db, ccache, optidx, argc, argv);
+
+ if(ccache != NULL)
+ krb5_cc_destroy(context, ccache);
+
+ if(db != NULL)
+ (*db->hdb_destroy)(context, db);
+
+ krb5_free_context(context);
+ return exit_code;
+}
diff --git a/third_party/heimdal/kdc/hprop.h b/third_party/heimdal/kdc/hprop.h
new file mode 100644
index 0000000..59c39ea
--- /dev/null
+++ b/third_party/heimdal/kdc/hprop.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 1997 - 2000 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* $Id$ */
+
+#ifndef __HPROP_H__
+#define __HPROP_H__
+
+#include "headers.h"
+
+struct prop_data{
+ krb5_context context;
+ krb5_auth_context auth_context;
+ int sock;
+};
+
+#define HPROP_VERSION "hprop-0.0"
+#define HPROP_NAME "hprop"
+#define HPROP_KEYTAB "HDBGET:"
+#define HPROP_PORT 754
+
+#ifndef NEVERDATE
+#define NEVERDATE ((1U << 31) - 1)
+#endif
+
+krb5_error_code v5_prop(krb5_context, HDB*, hdb_entry*, void*);
+int mit_prop_dump(void*, const char*);
+
+#endif /* __HPROP_H__ */
diff --git a/third_party/heimdal/kdc/hpropd-version.rc b/third_party/heimdal/kdc/hpropd-version.rc
new file mode 100644
index 0000000..388d64d
--- /dev/null
+++ b/third_party/heimdal/kdc/hpropd-version.rc
@@ -0,0 +1,36 @@
+/***********************************************************************
+ * Copyright (c) 2010, Secure Endpoints Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ **********************************************************************/
+
+#define RC_FILE_TYPE VFT_APP
+#define RC_FILE_DESC_0409 "Propagated KDC database recipient"
+#define RC_FILE_ORIG_0409 "hpropd.exe"
+
+#include "../windows/version.rc"
diff --git a/third_party/heimdal/kdc/hpropd.8 b/third_party/heimdal/kdc/hpropd.8
new file mode 100644
index 0000000..9056b05
--- /dev/null
+++ b/third_party/heimdal/kdc/hpropd.8
@@ -0,0 +1,87 @@
+.\" Copyright (c) 1997, 2000 - 2003 Kungliga Tekniska Högskolan
+.\" (Royal Institute of Technology, Stockholm, Sweden).
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\"
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\"
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" 3. Neither the name of the Institute nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $Id$
+.\"
+.Dd August 27, 1997
+.Dt HPROPD 8
+.Os HEIMDAL
+.Sh NAME
+.Nm hpropd
+.Nd receive a propagated database
+.Sh SYNOPSIS
+.Nm
+.Bk -words
+.Oo Fl d Ar file \*(Ba Xo
+.Fl Fl database= Ns Ar file
+.Xc
+.Oc
+.Op Fl n | Fl Fl stdin
+.Op Fl Fl print
+.Op Fl i | Fl Fl no-inetd
+.Oo Fl k Ar keytab \*(Ba Xo
+.Fl Fl keytab= Ns Ar keytab
+.Xc
+.Oc
+.Ek
+.Sh DESCRIPTION
+.Nm
+receives a database sent by
+.Nm hprop .
+and writes it as a local database.
+.Pp
+By default,
+.Nm
+expects to be started from
+.Nm inetd
+if stdin is a socket and expects to receive the dumped database over
+stdin otherwise.
+If the database is sent over the network, it is authenticated and
+encrypted.
+Only connections authenticated with the principal
+.Nm kadmin Ns / Ns Nm hprop
+are accepted.
+.Pp
+Options supported:
+.Bl -tag -width Ds
+.It Fl d Ar file , Fl Fl database= Ns Ar file
+database
+.It Fl n , Fl Fl stdin
+read from stdin
+.It Fl Fl print
+print dump to stdout
+.It Fl i , Fl Fl no-inetd
+not started from inetd
+.It Fl k Ar keytab , Fl Fl keytab= Ns Ar keytab
+keytab to use for authentication
+.El
+.Sh SEE ALSO
+.Xr hprop 8
diff --git a/third_party/heimdal/kdc/hpropd.c b/third_party/heimdal/kdc/hpropd.c
new file mode 100644
index 0000000..255d609
--- /dev/null
+++ b/third_party/heimdal/kdc/hpropd.c
@@ -0,0 +1,285 @@
+/*
+ * Copyright (c) 1997-2006 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "hprop.h"
+
+static int inetd_flag = -1;
+static int help_flag;
+static int version_flag;
+static int print_dump;
+static const char *database;
+static int from_stdin;
+static char *local_realm;
+static char *ktname = NULL;
+
+struct getargs args[] = {
+ { "database", 'd', arg_string, rk_UNCONST(&database), "database", "file" },
+ { "stdin", 'n', arg_flag, &from_stdin, "read from stdin", NULL },
+ { "print", 0, arg_flag, &print_dump, "print dump to stdout", NULL },
+#ifdef SUPPORT_INETD
+ { "inetd", 'i', arg_negative_flag, &inetd_flag,
+ "Not started from inetd", NULL },
+#endif
+ { "keytab", 'k', arg_string, &ktname, "keytab to use for authentication", "keytab" },
+ { "realm", 'r', arg_string, &local_realm, "realm to use", NULL },
+ { "version", 0, arg_flag, &version_flag, NULL, NULL },
+ { "help", 'h', arg_flag, &help_flag, NULL, NULL}
+};
+
+static int num_args = sizeof(args) / sizeof(args[0]);
+static char unparseable_name[] = "unparseable name";
+
+static void
+usage(int ret)
+{
+ arg_printusage (args, num_args, NULL, "");
+ exit (ret);
+}
+
+int
+main(int argc, char **argv)
+{
+ krb5_error_code ret;
+ krb5_context context;
+ krb5_auth_context ac = NULL;
+ krb5_principal c1, c2;
+ krb5_authenticator authent;
+ krb5_keytab keytab;
+ krb5_socket_t sock = rk_INVALID_SOCKET;
+ HDB *db = NULL;
+ int optidx = 0;
+ char *tmp_db = NULL;
+ krb5_log_facility *fac;
+ int nprincs;
+
+ setprogname(argv[0]);
+
+ ret = krb5_init_context(&context);
+ if (ret)
+ exit(1);
+
+ ret = krb5_openlog(context, "hpropd", &fac);
+ if (ret)
+ errx(1, "krb5_openlog");
+ krb5_set_warn_dest(context, fac);
+
+ if (getarg(args, num_args, argc, argv, &optidx))
+ usage(1);
+
+ if (local_realm != NULL)
+ krb5_set_default_realm(context, local_realm);
+
+ if (help_flag)
+ usage(0);
+ if (version_flag) {
+ print_version(NULL);
+ exit(0);
+ }
+
+ argc -= optidx;
+ argv += optidx;
+
+ if (argc != 0)
+ usage(1);
+
+ if (database == NULL)
+ database = hdb_default_db(context);
+
+ if (from_stdin) {
+ sock = STDIN_FILENO;
+ } else {
+ struct sockaddr_storage ss;
+ struct sockaddr *sa = (struct sockaddr *)&ss;
+ socklen_t sin_len = sizeof(ss);
+ char addr_name[256];
+ krb5_ticket *ticket;
+ char *server;
+
+ memset(&ss, 0, sizeof(ss));
+ sock = STDIN_FILENO;
+#ifdef SUPPORT_INETD
+ if (inetd_flag == -1) {
+ if (getpeername (sock, sa, &sin_len) < 0) {
+ inetd_flag = 0;
+ } else {
+ inetd_flag = 1;
+ }
+ }
+#else
+ inetd_flag = 0;
+#endif
+ if (!inetd_flag) {
+ mini_inetd (krb5_getportbyname (context, "hprop", "tcp",
+ HPROP_PORT), &sock);
+ }
+ socket_set_keepalive(sock, 1);
+ sin_len = sizeof(ss);
+ if (getpeername(sock, sa, &sin_len) < 0)
+ krb5_err(context, 1, errno, "getpeername");
+
+ if (inet_ntop(sa->sa_family,
+ socket_get_address (sa),
+ addr_name,
+ sizeof(addr_name)) == NULL)
+ strlcpy (addr_name, "unknown address",
+ sizeof(addr_name));
+
+ krb5_log(context, fac, 0, "Connection from %s", addr_name);
+
+ ret = krb5_kt_register(context, &hdb_get_kt_ops);
+ if (ret)
+ krb5_err(context, 1, ret, "krb5_kt_register");
+
+ if (ktname != NULL) {
+ ret = krb5_kt_resolve(context, ktname, &keytab);
+ if (ret)
+ krb5_err (context, 1, ret, "krb5_kt_resolve %s", ktname);
+ } else {
+ ret = krb5_kt_default (context, &keytab);
+ if (ret)
+ krb5_err (context, 1, ret, "krb5_kt_default");
+ }
+
+ ret = krb5_recvauth(context, &ac, &sock, HPROP_VERSION, NULL,
+ 0, keytab, &ticket);
+ if (ret)
+ krb5_err(context, 1, ret, "krb5_recvauth");
+
+ ret = krb5_unparse_name(context, ticket->server, &server);
+ if (ret)
+ krb5_err(context, 1, ret, "krb5_unparse_name");
+ if (strncmp(server, "hprop/", 6) != 0)
+ krb5_errx(context, 1, "ticket not for hprop (%s)", server);
+
+ free(server);
+ krb5_free_ticket (context, ticket);
+
+ ret = krb5_auth_con_getauthenticator(context, ac, &authent);
+ if (ret)
+ krb5_err(context, 1, ret, "krb5_auth_con_getauthenticator");
+
+ ret = krb5_make_principal(context, &c1, NULL, "kadmin", "hprop", NULL);
+ if (ret)
+ krb5_err(context, 1, ret, "krb5_make_principal");
+ _krb5_principalname2krb5_principal(context, &c2,
+ authent->cname, authent->crealm);
+ if (!krb5_principal_compare(context, c1, c2)) {
+ char *s;
+ ret = krb5_unparse_name(context, c2, &s);
+ if (ret)
+ s = unparseable_name;
+ krb5_errx(context, 1, "Unauthorized connection from %s", s);
+ }
+ krb5_free_principal(context, c1);
+ krb5_free_principal(context, c2);
+
+ ret = krb5_kt_close(context, keytab);
+ if (ret)
+ krb5_err(context, 1, ret, "krb5_kt_close");
+ }
+
+ if (asprintf(&tmp_db, "%s~", database) < 0 || tmp_db == NULL)
+ krb5_errx(context, 1, "hdb_create: out of memory");
+
+ ret = hdb_create(context, &db, tmp_db);
+ if (ret)
+ krb5_err(context, 1, ret, "hdb_create(%s)", tmp_db);
+ ret = db->hdb_open(context, db, O_RDWR | O_CREAT | O_TRUNC, 0600);
+ if (ret)
+ krb5_err(context, 1, ret, "hdb_open(%s)", tmp_db);
+
+ nprincs = 0;
+ while (1){
+ krb5_data data;
+ hdb_entry entry;
+
+ if (from_stdin) {
+ ret = krb5_read_message(context, &sock, &data);
+ if (ret != 0 && ret != HEIM_ERR_EOF)
+ krb5_err(context, 1, ret, "krb5_read_message");
+ } else {
+ ret = krb5_read_priv_message(context, ac, &sock, &data);
+ if (ret)
+ krb5_err(context, 1, ret, "krb5_read_priv_message");
+ }
+
+ if (ret == HEIM_ERR_EOF || data.length == 0) {
+ if (!from_stdin) {
+ data.data = NULL;
+ data.length = 0;
+ krb5_write_priv_message(context, ac, &sock, &data);
+ }
+ break;
+ }
+ memset(&entry, 0, sizeof(entry));
+ ret = hdb_value2entry(context, &data, &entry);
+ krb5_data_free(&data);
+ if (ret)
+ krb5_err(context, 1, ret, "hdb_value2entry");
+ if (print_dump) {
+ struct hdb_print_entry_arg parg;
+
+ parg.out = stdout;
+ parg.fmt = HDB_DUMP_HEIMDAL;
+ hdb_print_entry(context, db, &entry, &parg);
+ } else {
+ ret = db->hdb_store(context, db, 0, &entry);
+ if (ret == HDB_ERR_EXISTS) {
+ char *s;
+ ret = krb5_unparse_name(context, entry.principal, &s);
+ if (ret)
+ s = strdup(unparseable_name);
+ krb5_warnx(context, "Entry exists: %s", s);
+ free(s);
+ } else if (ret)
+ krb5_err(context, 1, ret, "db_store");
+ else
+ nprincs++;
+ }
+ hdb_free_entry(context, db, &entry);
+ }
+ if (!print_dump)
+ krb5_log(context, fac, 0, "Received %d principals", nprincs);
+
+ ret = db->hdb_close(context, db);
+ if (ret)
+ krb5_err(context, 1, ret, "db_close");
+ ret = db->hdb_rename(context, db, database);
+ if (ret)
+ krb5_err(context, 1, ret, "db_rename");
+
+ if (inetd_flag == 0)
+ rk_closesocket(sock);
+
+ exit(0);
+}
diff --git a/third_party/heimdal/kdc/httpkadmind.8 b/third_party/heimdal/kdc/httpkadmind.8
new file mode 100644
index 0000000..345e959
--- /dev/null
+++ b/third_party/heimdal/kdc/httpkadmind.8
@@ -0,0 +1,653 @@
+.\" Copyright (c) 2020 Kungliga Tekniska Högskolan
+.\" (Royal Institute of Technology, Stockholm, Sweden).
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\"
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\"
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" 3. Neither the name of the Institute nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.Dd January 2, 2020
+.Dt HTTPKADMIND 8
+.Os HEIMDAL
+.Sh NAME
+.Nm httpkadmind
+.Nd HTTP HDB Administration Interface
+.Sh SYNOPSIS
+.Nm
+.Op Fl h | Fl Fl help
+.Op Fl Fl version
+.Op Fl H Ar HOSTNAME
+.Op Fl d | Fl Fl daemon
+.Op Fl Fl daemon-child
+.Op Fl Fl reverse-proxied
+.Op Fl p Ar port number (default: 443)
+.Op Fl Fl allow-GET
+.Op Fl Fl no-allow-GET
+.Op Fl Fl GET-with-csrf-token
+.Op Fl Fl csrf-header= Ns Ar HEADER-NAME
+.Op Fl Fl temp-dir= Ns Ar DIRECTORY
+.Op Fl Fl cert=HX509-STORE
+.Op Fl Fl private-key=HX509-STORE
+.Op Fl T | Fl Fl token-authentication-type=Negotiate|Bearer
+.Op Fl Fl realm=REALM
+.Op Fl Fl read-only
+.Op Fl l | Fl Fl local
+.Op Fl Fl local-read-only
+.Op Fl Fl hdb=HDB
+.Op Fl Fl stash-file=FILENAME
+.Op Fl Fl primary-server-uri=URI
+.Op Fl Fl read-only-admin-server=HOSTNAME[:PORT]
+.Op Fl Fl writable-admin-server=HOSTNAME[:PORT]
+.Op Fl Fl kadmin-client-name=PRINCIPAL
+.Op Fl Fl kadmin-client-keytab=KEYTAB
+.Op Fl t | Fl Fl thread-per-client
+.Oo Fl v \*(Ba Xo
+.Fl Fl verbose= Ns Ar run verbosely
+.Xc
+.Oc
+.Sh DESCRIPTION
+Serves the following resources over HTTP:
+.Ar /get-keys and
+.Ar /get-config .
+.Pp
+The
+.Ar /get-keys
+end-point allows callers to get a principal's keys in
+.Dq keytab
+format for named principals, possibly performing write operations
+such as creating a non-existent principal, or rotating its keys,
+if requested.
+Note that this end-point can cause KDC HDB principal entries to
+be modified or created incidental to fetching the principal's
+keys.
+The use of the HTTP POST method is required when this end-point
+writes to the KDC's HDB.
+See below.
+.Pp
+The
+.Ar /get-config
+end-point allows callers to get
+.Nm krb5.conf
+contents for a given principal.
+.Pp
+This service can run against a local HDB, or against a remote HDB
+via the
+.Nm kadmind(8)
+protocol.
+Read operations are always allowed, but write operations can be
+preformed either against writable
+.Nm kadmind(8)
+server(s) or redirected to another
+.Nm httpkadmind(8).
+.Pp
+The
+.Ar /get-config
+end-point accepts a single query parameter:
+.Bl -tag -width Ds -offset indent
+.It Ar princ=PRINCIPAL .
+.El
+.Sh HTTP APIS
+All HTTP APIs served by this program accept POSTs, with all
+request parameters given as either URI query parameters, and/or
+as form data in the POST request body, in either
+.Ar application/x-www-form-urlencoded
+or
+.Ar multipart/formdata .
+If GETs are enabled, then request parameters must be supplied as
+URI query parameters.
+.Pp
+Note that requests that cause changes to the HDB must always be
+done via POST, never GET.
+.Pp
+URI query parameters must be of the form
+.Ar param0=value&param1=value...
+Some parameters can be given multiple values -- see the
+descriptions of the end-points.
+.Sh CROSS-SITE REQUEST FORGERY PROTECTION
+.Em None
+of the resources service by this service are intended to be
+executed by web pages.
+.Pp
+Most of the resources provided by this service are
+.Dq safe
+in the sense that they do not change server-side state besides
+logging, and in that they are idempotent, but they are
+only safe to execute
+.Em if and only if
+the requesting party is trusted to see the response.
+Since none of these resources are intended to be used from web
+pages, it is important that web pages not be able to execute them
+.Em and
+observe the responses.
+.Pp
+Some of the resources provided by this service do change
+server-side state, specifically principal entries in the KDC's
+HDB.
+Those always require the use of POST, not GET.
+.Pp
+In a web browser context, pages from other origins will be able
+to attempt requests to this service, but should never be able to
+see the responses because browsers normally wouldn't allow that.
+Nonetheless, anti cross site request forgery (CSRF) protection
+may be desirable.
+.Pp
+This service provides the following CSRF protection features:
+.Bl -tag -width Ds -offset indent
+.It requests are rejected if they have a
+.Dq Referer
+(except the experimental /get-negotiate-token end-point)
+.It the service can be configured to require a header that would make the
+request not Dq simple
+.It GETs can be disabled (see options), thus requiring POSTs
+.It GETs can be required to have a CSRF token (see below)
+.It POSTs can be required to have a CSRF token
+.El
+.Pp
+The experimental
+.Ar /get-negotiate-token
+end-point, however, always accepts
+.Dq Referer
+requests.
+.Pp
+To obtain a CSRF token, first execute the request without the
+CSRF token, and the resulting error
+response will include a
+.Ar X-CSRF-Token
+response header.
+.Pp
+To execute a request with a CSRF token, first obtain a CSRF token
+as described above, then copy the token to the request as the
+value of the request's
+.Ar X-CSRF-Token
+header.
+.Pp
+The key for keying the CSRF token HMAC is that of the first
+current key for the
+.Sq WELLKNOWN/CSRFTOKEN
+principal for the realm being used.
+Every realm served by this service must have this principal.
+.Sh GETTING KEYTABS
+The
+.Ar /get-keys
+end-point accepts various parameters:
+.Bl -tag -width Ds -offset indent
+.It Ar spn=PRINCIPAL
+Names the host-based service principal whose keys to get.
+May be given multiple times, and all named principal's keys will
+be fetched.
+.It Ar dNSName=HOSTNAME
+Names the host-based service principal's hostname whose keys to get.
+May be given multiple times, and all named principal's keys will
+be fetched.
+.It Ar service=SERVICE
+Hostnames given with
+.Ar dNSName=HOSTNAME
+will be qualified with this service name to form a host-based
+service principal.
+May be given multiple times, in which case the cartesian product
+of
+.Ar dNSName=HOSTNAME
+ad
+.Ar service=SERVICE
+will be used.
+Defaults to
+.Ar HTTP .
+.It realm=REALM
+Must be present if the
+.Nm httpkadmind
+daemon's default realm is not desired.
+.It Ar enctypes=ENCTYPE,...
+A comma-separated list of enctypes that the principal is expected
+to support (used for Kerberos Ticket session key negotiation).
+Defaults to the
+.Ar supported_enctypes
+configured in
+.Nm krb5.conf(5) .
+.It Ar materialize=true
+If the named principal(s) is (are) virtual, this will cause it
+(them) to be materialized as a concrete principal.
+(Currently not supported.)
+.It Ar create=true
+If the named principal(s) does not (do not) exist, this will
+cause it (them) to be created.
+The default attributes for new principals created this way will
+be taken (except for the disabled attribute) from any containing
+virtual host-based service principal namespace that include a
+leading
+.Sq .
+in the hostname component, or from
+.Nm krb5.conf(5)
+(see the CONFIGURATION section).
+.It Ar rotate=true
+This will cause the keys of concrete principals to be rotated.
+.It Ar revoke=true
+This will cause old keys of concrete principals to be deleted
+if their keys are being rotated.
+This means that extant service tickets with those principals as
+the target will not be able to be decrypted by the caller as it
+will not have the necessary keys.
+.El
+.Pp
+The HTTP
+.Nm Cache-Control
+header will be set on
+.Nm get-keys
+responses to
+.Dq Nm no-store ,
+and the
+.Nm max-age
+cache control parameter will be set to the least number of
+seconds until before any of the requested principal's keys could
+change.
+For virtual principals this will be either the time left until a
+quarter of the rotation period before the next rotation, or the
+time left until a
+quarter of the rotation period after the next rotation.
+For concrete principals this will be the time left to the first
+such principal's password expiration, or, if none of them have a
+configured password expiration time, then half of the
+.Nm new_service_key_delay
+configured in the
+.Nm [hdb]
+section of the
+.Nm krb5.conf(5)
+file.
+.Pp
+Authorization is handled via the same mechanism as in
+.Nm bx509d(8)
+which was originally intended to authorize certification requests
+(CSRs).
+Authorization for extracting keys is specified like for
+.Nm bx509d(8) ,
+but using
+.Nm [ext_keytab]
+as the
+.Nm krb5.conf(5) section.
+Clients with host-based principals for the
+.Dq host
+service can create and extract keys for their own service name,
+but otherwise a number of service names are denied:
+.Bl -tag -width Ds -offset indent
+.It Dq host
+.It Dq root
+.It Dq exceed
+.El
+as well as all the service names for Heimdal-specific services:
+.Bl -tag -width Ds -offset indent
+.It Dq krbtgt
+.It Dq iprop
+.It Dq kadmin
+.It Dq hprop
+.It Dq WELLKNOWN
+.It Dq K
+.El
+.Pp
+Supported options:
+.Bl -tag -width Ds -offset indent
+.It Xo
+.Fl h ,
+.Fl Fl help
+.Xc
+Print usage message.
+.It Xo
+.Fl Fl version
+.Xc
+Print version.
+.It Xo
+.Fl H Ar HOSTNAME
+.Xc
+Expected audience(s) of bearer tokens (i.e., acceptor name).
+.It Xo
+.Fl d ,
+.Fl Fl daemon
+.Xc
+Detach from TTY and run in the background.
+.It Xo
+.Fl Fl reverse-proxied
+.Xc
+Serves HTTP instead of HTTPS, accepting only looped-back connections.
+.It Xo
+.Fl p Ar port number (default: 443)
+.Xc
+PORT
+.It Xo
+.Fl Fl allow-GET
+.Xc
+If given, then HTTP GET will be allowed for the various end-points
+other than
+.Ar /health .
+Otherwise only HEAD and POST will be allowed.
+By default GETs are allowed, but this will change soon.
+.It Xo
+.Fl Fl no-allow-GET
+.Xc
+If given then HTTP GETs will be rejected for the various
+end-points other than
+.Ar /health .
+.It Xo
+.Fl Fl csrf-protection-type= Ns Ar CSRF-PROTECTION-TYPE
+.Xc
+Possible values of
+.Ar CSRF-PROTECTION-TYPE
+are
+.Bl -bullet -compact -offset indent
+.It
+.Li GET-with-header
+.It
+.Li GET-with-token
+.It
+.Li POST-with-header
+.It
+.Li POST-with-token
+.El
+This may be given multiple times.
+The default is to require CSRF tokens for POST requests, and to
+require neither a non-simple header nor a CSRF token for GET
+requests.
+.Pp
+See
+.Sx CROSS-SITE REQUEST FORGERY PROTECTION .
+.It Xo
+.Fl Fl csrf-header= Ns Ar HEADER-NAME
+.Xc
+If given, then all requests other than to the
+.Ar /health
+service must have the given request
+.Ar HEADER-NAME
+set (the value is irrelevant).
+.It Xo
+.Fl Fl temp-dir= Ns Ar DIRECTORY
+.Xc
+Directory for temp files.
+If not specified then a temporary directory will be made.
+.It Xo
+.Fl Fl cert= Ns Ar HX509-STORE
+.Xc
+Certificate file path (PEM) for HTTPS service.
+May contain private key as well.
+.It Xo
+.Fl Fl private-key= Ns Ar HX509-STORE
+.Xc
+Private key file path (PEM), if the private key is not stored along with the
+certificiate.
+.It Xo
+.Fl T Ar HTTP-AUTH-TYPE,
+.Fl Fl token-authentication-type= Ns Ar HTTP-AUTH-TYPE
+.Xc
+HTTP bearer token authentication type(s) supported (may be given more than
+once).
+For example,
+.Ar Negotiate
+or
+.Ar Bearer
+(JWT).
+.It Xo
+.Fl t ,
+.Fl Fl thread-per-client
+.Xc
+Uses a thread per-client instead of as many threads as there are CPUs.
+.It Xo
+.Fl Fl realm= Ns Ar REALM
+.Xc
+The realm to serve, if not the default realm.
+Note that clients can request keys for principals in other realms, and
+.Nm httpkadmind
+will attempt to satisfy those requests too.
+.It Xo
+.Fl Fl read-only
+.Xc
+Do not perform write operations.
+Write operations will either fail or if a primary
+.Nm httpkadmind
+URI is configured, then they will be redirected there.
+.It Xo
+.Fl Fl local
+.Xc
+Use a local HDB, at least for read operations, and, if
+.Fl Fl local-read-only
+is not given, then also write operations.
+.It Xo
+.Fl Fl local-read-only
+.Xc
+Do not perform writes on a local HDB.
+Either redirect write operations if a primary
+.Nm httpkadmind
+URI is configured, or use a writable remote
+.Nm kadmind
+server.
+.It Xo
+.Fl Fl hdb=HDB
+.Xc
+A local HDB to serve.
+Note that this can be obtained from the
+.Nm krb5.conf .
+.It Xo
+.Fl Fl stash-file=FILENAME
+.Xc
+A stash file containing a master key for a local HDB.
+Note that this can be obtained from the
+.Nm krb5.conf .
+.It Xo
+.Fl Fl primary-server-uri=URI
+.Xc
+The URL of an httpkadmind to which to redirect write operations.
+.It Xo
+.Fl Fl read-only-admin-server=HOSTNAME[:PORT]
+.Xc
+The hostname (and possibly port number) of a
+.Nm kadmind(8)
+service to use for read-only operations.
+Recall that the
+.Nm kadmind(8)
+service's principal name is
+.Ar kadmin/admin .
+The
+.Ar HOSTNAME
+given here can be a name that resolves to the IP addresses of all
+the
+.Nm kadmind(8)
+services for the
+.Ar REALM .
+If not specified, but needed, this will be obtained by looking for
+.Nm readonly_admin_server
+in
+.Nm krb5.conf
+or, if enabled, performing
+DNS lookups for SRV resource records named
+.Ar _kerberos-adm-readonly._tcp.<realm> .
+.It Xo
+.Fl Fl writable-admin-server=HOSTNAME[:PORT]
+.Xc
+The hostname (and possibly port number) of a
+.Nm kadmind(8)
+service to use for write operations.
+If not specified, but needed, this will be obtained by looking for
+.Nm admin_server
+in
+.Nm krb5.conf
+or, if enabled, performing DNS lookups for SRV resource records named
+.Ar _kerberos-adm._tcp.<realm> .
+.It Xo
+.Fl Fl kadmin-client-name=PRINCIPAL
+.Xc
+The client principal name to use when connecting to a
+.Nm kadmind(8)
+server.
+Defaults to
+.Ar httpkadmind/admin .
+.It Xo
+.Fl Fl kadmin-client-keytab=KEYTAB
+.Xc
+The keytab containing keys for the
+.Ar kadmin-client-name .
+Note that you may use an
+.Ar HDB
+as a keytab as
+.Ar HDBGET:/var/heimdal/heimdal.db
+(or whatever the HDB specification is).
+.It Xo
+.Fl v ,
+.Fl Fl verbose= Ns Ar run verbosely
+.Xc
+verbose
+.El
+.Sh ENVIRONMENT
+.Bl -tag -width Ds
+.It Ev KRB5_CONFIG
+The file name of
+.Pa krb5.conf ,
+the default being
+.Pa /etc/krb5.conf .
+.El
+.Sh FILES
+.Bl -tag -width Ds
+.It Pa /etc/krb5.conf
+.El
+.Sh CONFIGURATION
+Authorizer configuration goes in
+.Br
+.Ar [ext_keytab]
+in
+.Nm krb5.conf(5). For example:
+.Pp
+.Bd -literal -offset indent
+[ext_keytab]
+ simple_csr_authorizer_directory = /etc/krb5/simple_csr_authz
+ ipc_csr_authorizer = {
+ service = UNIX:/var/heimdal/csr_authorizer_sock
+ }
+.Ed
+.Pp
+Configuration parameters specific to
+.Nm httpkadmind :
+.Bl -tag -width Ds -offset indent
+.It csr_authorizer_handles_svc_names = BOOL
+.It new_hostbased_service_principal_attributes = ...
+.El
+.Pp
+The
+.Nm [ext_keytab]
+.Nm get_keys_max_spns = NUMBER
+parameter can be used to specify a maximum number of principals whose
+keys can be retrieved in one
+.Nm GET
+of the
+.Nm /get-keys
+end-point.
+Defaults to 400.
+.Pp
+The
+.Nm [ext_keytab]
+.Nm new_hostbased_service_principal_attributes
+parameter may be used instead of virtual host-based service
+namespace principals to specify the attributes of new principals
+created by
+.Nm httpkadmind ,
+and its value is a hive with a service name then a hostname or
+namespace, and whose value is a set of attributes as given in the
+.Nm kadmin(1) modify
+command.
+For example:
+.Bd -literal -offset indent
+[ext_keytab]
+ new_hostbased_service_principal_attributes = {
+ host = {
+ a-particular-hostname.test.h5l.se = ok-as-delegate
+ .prod.test.h5l.se = ok-as-delegate
+ }
+ }
+.Ed
+.Pp
+which means that
+.Dq host/a-particular-hostname.test.h5l.se ,
+if created via
+.Nm httpkadmind ,
+will be allowed to get delegated credentials (ticket forwarding),
+and that hostnames matching the glob pattern
+.Dq host/*.prod.test.h5l.se ,
+if created via
+.Nm httpkadmind ,
+will also allowed to get delegated credentials.
+All host-based service principals created via
+.Nm httpkadmind
+not matchining any
+.Nm new_hostbased_service_principal_attributes
+service namespaces will have the empty attribute set.
+.Sh EXAMPLES
+To start
+.Nm httpkadmind
+on a primary KDC:
+.Pp
+.Ar $ httpkadmind -d --cert=PEM-FILE:/etc/httpkadmind.pem
+\\
+.Br
+ --local -T Negotiate
+.Pp
+To start
+.Nm httpkadmind
+on a secondary KDC, using redirects for write operations:
+.Pp
+.Ar $ httpkadmind -d --cert=PEM-FILE:/etc/httpkadmind.pem
+\\
+.Br
+ --local-read-only -T Negotiate
+\\
+.Br
+ --primary-server-uri=https://the-primary-server.fqdn/
+.Pp
+To start
+.Nm httpkadmind
+on a secondary KDC, proxying kadmin to perform writes at the primary KDC, using
+DNS to discover the kadmin server:
+.Pp
+.Ar $ httpkadmind -d --cert=PEM-FILE:/etc/httpkadmind.pem
+\\
+.Br
+ --local-read-only -T Negotiate
+\\
+.Br
+ --kadmin-client-keytab=FILE:/etc/krb5.keytab
+.Pp
+To start
+.Nm httpkadmind
+on a non-KDC:
+.Pp
+.Ar $ httpkadmind -d --cert=PEM-FILE:/etc/httpkadmind.pem
+\\
+.Br
+ -T Negotiate --kadmin-client-keytab=FILE:/etc/krb5.keytab
+.Pp
+.Sh DIAGNOSTICS
+See logging section of
+.Nm krb5.conf.5
+.Sh SEE ALSO
+.Xr bx509d 8 ,
+.Xr kadmin 1 ,
+.Xr kadmind 8 ,
+.Xr krb5.conf 5 .
+.\".Sh STANDARDS
+.\".Sh HISTORY
+.\".Sh AUTHORS
+.\".Sh BUGS
diff --git a/third_party/heimdal/kdc/httpkadmind.c b/third_party/heimdal/kdc/httpkadmind.c
new file mode 100644
index 0000000..1d64e84
--- /dev/null
+++ b/third_party/heimdal/kdc/httpkadmind.c
@@ -0,0 +1,2762 @@
+/*
+ * Copyright (c) 2020 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ */
+
+#define _XOPEN_SOURCE_EXTENDED 1
+#define _DEFAULT_SOURCE 1
+#define _BSD_SOURCE 1
+#define _GNU_SOURCE 1
+
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <ctype.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+
+#include <microhttpd.h>
+#include "kdc_locl.h"
+#include "token_validator_plugin.h"
+#include <getarg.h>
+#include <roken.h>
+#include <krb5.h>
+#include <gssapi/gssapi.h>
+#include <gssapi/gssapi_krb5.h>
+#include <hx509.h>
+#include "../lib/hx509/hx_locl.h"
+#include <hx509-private.h>
+#include <kadm5/admin.h>
+#include <kadm5/private.h>
+#include <kadm5/kadm5_err.h>
+
+#define heim_pcontext krb5_context
+#define heim_pconfig krb5_context
+#include <heimbase-svc.h>
+
+#if MHD_VERSION < 0x00097002 || defined(MHD_YES)
+/* libmicrohttpd changed these from int valued macros to an enum in 0.9.71 */
+#ifdef MHD_YES
+#undef MHD_YES
+#undef MHD_NO
+#endif
+enum MHD_Result { MHD_NO = 0, MHD_YES = 1 };
+#define MHD_YES 1
+#define MHD_NO 0
+typedef int heim_mhd_result;
+#else
+typedef enum MHD_Result heim_mhd_result;
+#endif
+
+#define BODYLEN_IS_STRLEN (~0)
+
+/*
+ * Libmicrohttpd is not the easiest API to use. It's got issues.
+ *
+ * One of the issues is how responses are handled, and the return value of the
+ * resource handler (MHD_NO -> close the connection, MHD_YES -> send response).
+ * Note that the handler could return MHD_YES without having set an HTTP
+ * response.
+ *
+ * There's memory management issues as well.
+ *
+ * Here we have to be careful about return values.
+ *
+ * Some of the functions defined here return just a krb5_error_code without
+ * having set an HTTP response on error.
+ * Others do set an HTTP response on error.
+ * The convention is to either set an HTTP response on error, or not at all,
+ * but not a mix of errors where for some the function will set a response and
+ * for others it won't.
+ *
+ * We do use some system error codes to stand in for errors here.
+ * Specifically:
+ *
+ * - EACCES -> authorization failed
+ * - EINVAL -> bad API usage
+ * - ENOSYS -> missing CSRF token but CSRF token required
+ *
+ * FIXME: We should rely only on krb5_set_error_message() and friends and make
+ * error responses only in route(), mapping krb5_error_code values to
+ * HTTP status codes. This would simplify the error handling convention
+ * here.
+ */
+
+struct free_tend_list {
+ void *freeme1;
+ void *freeme2;
+ struct free_tend_list *next;
+};
+
+/* Our request description structure */
+typedef struct kadmin_request_desc {
+ HEIM_SVC_REQUEST_DESC_COMMON_ELEMENTS;
+
+ struct MHD_Connection *connection;
+ krb5_times token_times;
+ /*
+ * FIXME
+ *
+ * Currently we re-use the authz framework from bx509d, using an
+ * `hx509_request' instance (an abstraction for CSRs) to represent the
+ * request because that is what the authz plugin uses that implements the
+ * policy we want checked here.
+ *
+ * This is inappropriate in the long-term in two ways:
+ *
+ * - the policy for certificates deals in SANs and EKUs, whereas the
+ * policy for ext_keytab deals in host-based service principal names,
+ * and there is not a one-to-one mapping of service names to EKUs;
+ *
+ * - using a type from libhx509 for representing requests for things that
+ * aren't certificates is really not appropriate no matter how similar
+ * the use cases for this all might be.
+ *
+ * What we need to do is develop a library that can represent requests for
+ * credentials via naming attributes like SANs and Kerberos principal
+ * names, but more arbitrary still than what `hx509_request' supports, and
+ * then invokes a plugin.
+ *
+ * Also, we might want to develop an in-tree authorization solution that is
+ * richer than what kadmin.acl supports now, storing grants in HDB entries
+ * and/or similar places.
+ *
+ * For expediency we use `hx509_request' here for now, impedance mismatches
+ * be damned.
+ */
+ hx509_request req; /* For authz only */
+ struct free_tend_list *free_list;
+ struct MHD_PostProcessor *pp;
+ heim_array_t service_names;
+ heim_array_t hostnames;
+ heim_array_t spns;
+ krb5_principal cprinc;
+ krb5_keytab keytab;
+ krb5_storage *sp;
+ void *kadm_handle;
+ char *realm;
+ char *keytab_name;
+ char *freeme1;
+ char *enctypes;
+ char *cache_control;
+ char *csrf_token;
+ const char *method;
+ krb5_timestamp pw_end;
+ size_t post_data_size;
+ unsigned int response_set:1;
+ unsigned int materialize:1;
+ unsigned int rotate_now:1;
+ unsigned int rotate:1;
+ unsigned int revoke:1;
+ unsigned int create:1;
+ unsigned int ro:1;
+ unsigned int is_self:1;
+ char frombuf[128];
+} *kadmin_request_desc;
+
+static void
+audit_trail(kadmin_request_desc r, krb5_error_code ret)
+{
+ const char *retname = NULL;
+
+ /*
+ * Get a symbolic name for some error codes.
+ *
+ * Really, libcom_err should have a primitive for this, and ours could, but
+ * we can't use a system libcom_err if we extend ours.
+ */
+#define CASE(x) case x : retname = #x; break
+ switch (ret) {
+ case ENOSYS: retname = "ECSRFTOKENREQD"; break;
+ CASE(EINVAL);
+ CASE(ENOMEM);
+ CASE(EACCES);
+ CASE(HDB_ERR_NOT_FOUND_HERE);
+ CASE(HDB_ERR_WRONG_REALM);
+ CASE(HDB_ERR_EXISTS);
+ CASE(HDB_ERR_KVNO_NOT_FOUND);
+ CASE(HDB_ERR_NOENTRY);
+ CASE(HDB_ERR_NO_MKEY);
+ CASE(KRB5_KDC_UNREACH);
+ CASE(KADM5_FAILURE);
+ CASE(KADM5_AUTH_GET);
+ CASE(KADM5_AUTH_ADD);
+ CASE(KADM5_AUTH_MODIFY);
+ CASE(KADM5_AUTH_DELETE);
+ CASE(KADM5_AUTH_INSUFFICIENT);
+ CASE(KADM5_BAD_DB);
+ CASE(KADM5_DUP);
+ CASE(KADM5_RPC_ERROR);
+ CASE(KADM5_NO_SRV);
+ CASE(KADM5_BAD_HIST_KEY);
+ CASE(KADM5_NOT_INIT);
+ CASE(KADM5_UNK_PRINC);
+ CASE(KADM5_UNK_POLICY);
+ CASE(KADM5_BAD_MASK);
+ CASE(KADM5_BAD_CLASS);
+ CASE(KADM5_BAD_LENGTH);
+ CASE(KADM5_BAD_POLICY);
+ CASE(KADM5_BAD_PRINCIPAL);
+ CASE(KADM5_BAD_AUX_ATTR);
+ CASE(KADM5_BAD_HISTORY);
+ CASE(KADM5_BAD_MIN_PASS_LIFE);
+ CASE(KADM5_PASS_Q_TOOSHORT);
+ CASE(KADM5_PASS_Q_CLASS);
+ CASE(KADM5_PASS_Q_DICT);
+ CASE(KADM5_PASS_Q_GENERIC);
+ CASE(KADM5_PASS_REUSE);
+ CASE(KADM5_PASS_TOOSOON);
+ CASE(KADM5_POLICY_REF);
+ CASE(KADM5_INIT);
+ CASE(KADM5_BAD_PASSWORD);
+ CASE(KADM5_PROTECT_PRINCIPAL);
+ CASE(KADM5_BAD_SERVER_HANDLE);
+ CASE(KADM5_BAD_STRUCT_VERSION);
+ CASE(KADM5_OLD_STRUCT_VERSION);
+ CASE(KADM5_NEW_STRUCT_VERSION);
+ CASE(KADM5_BAD_API_VERSION);
+ CASE(KADM5_OLD_LIB_API_VERSION);
+ CASE(KADM5_OLD_SERVER_API_VERSION);
+ CASE(KADM5_NEW_LIB_API_VERSION);
+ CASE(KADM5_NEW_SERVER_API_VERSION);
+ CASE(KADM5_SECURE_PRINC_MISSING);
+ CASE(KADM5_NO_RENAME_SALT);
+ CASE(KADM5_BAD_CLIENT_PARAMS);
+ CASE(KADM5_BAD_SERVER_PARAMS);
+ CASE(KADM5_AUTH_LIST);
+ CASE(KADM5_AUTH_CHANGEPW);
+ CASE(KADM5_BAD_TL_TYPE);
+ CASE(KADM5_MISSING_CONF_PARAMS);
+ CASE(KADM5_BAD_SERVER_NAME);
+ CASE(KADM5_KS_TUPLE_NOSUPP);
+ CASE(KADM5_SETKEY3_ETYPE_MISMATCH);
+ CASE(KADM5_DECRYPT_USAGE_NOSUPP);
+ CASE(KADM5_POLICY_OP_NOSUPP);
+ CASE(KADM5_KEEPOLD_NOSUPP);
+ CASE(KADM5_AUTH_GET_KEYS);
+ CASE(KADM5_ALREADY_LOCKED);
+ CASE(KADM5_NOT_LOCKED);
+ CASE(KADM5_LOG_CORRUPT);
+ CASE(KADM5_LOG_NEEDS_UPGRADE);
+ CASE(KADM5_BAD_SERVER_HOOK);
+ CASE(KADM5_SERVER_HOOK_NOT_FOUND);
+ CASE(KADM5_OLD_SERVER_HOOK_VERSION);
+ CASE(KADM5_NEW_SERVER_HOOK_VERSION);
+ CASE(KADM5_READ_ONLY);
+ case 0:
+ retname = "SUCCESS";
+ break;
+ default:
+ retname = NULL;
+ break;
+ }
+ heim_audit_trail((heim_svc_req_desc)r, ret, retname);
+}
+
+static krb5_log_facility *logfac;
+static pthread_key_t k5ctx;
+
+static krb5_error_code
+get_krb5_context(krb5_context *contextp)
+{
+ krb5_error_code ret;
+
+ if ((*contextp = pthread_getspecific(k5ctx)))
+ return 0;
+
+ ret = krb5_init_context(contextp);
+ /* XXX krb5_set_log_dest(), warn_dest, debug_dest */
+ if (ret == 0)
+ (void) pthread_setspecific(k5ctx, *contextp);
+ return ret;
+}
+
+typedef enum {
+ CSRF_PROT_UNSPEC = 0,
+ CSRF_PROT_GET_WITH_HEADER = 1,
+ CSRF_PROT_GET_WITH_TOKEN = 2,
+ CSRF_PROT_POST_WITH_HEADER = 8,
+ CSRF_PROT_POST_WITH_TOKEN = 16,
+} csrf_protection_type;
+
+static csrf_protection_type csrf_prot_type = CSRF_PROT_UNSPEC;
+static int port = -1;
+static int help_flag;
+static int allow_GET_flag = -1;
+static int daemonize;
+static int daemon_child_fd = -1;
+static int local_hdb;
+static int local_hdb_read_only;
+static int read_only;
+static int verbose_counter;
+static int version_flag;
+static int reverse_proxied_flag;
+static int thread_per_client_flag;
+struct getarg_strings audiences;
+static getarg_strings csrf_prot_type_strs;
+static const char *csrf_header = "X-CSRF";
+static const char *cert_file;
+static const char *priv_key_file;
+static const char *cache_dir;
+static const char *realm;
+static const char *hdb;
+static const char *primary_server_URI;
+static const char *kadmin_server;
+static const char *writable_kadmin_server;
+static const char *stash_file;
+static const char *kadmin_client_name = "httpkadmind/admin";
+static const char *kadmin_client_keytab;
+static struct getarg_strings auth_types;
+
+#define set_conf(c, f, v, b) \
+ if (v) { \
+ if (((c).f = strdup(v)) == NULL) \
+ goto enomem; \
+ conf.mask |= b; \
+ }
+
+/*
+ * Does NOT set an HTTP response, naturally, as it doesn't even have access to
+ * the connection.
+ */
+static krb5_error_code
+get_kadm_handle(krb5_context context,
+ const char *want_realm,
+ int want_write,
+ void **kadm_handle)
+{
+ kadm5_config_params conf;
+ krb5_error_code ret;
+
+ /*
+ * If the caller wants to write and we are configured to redirect in that
+ * case, then trigger a redirect by returning KADM5_READ_ONLY.
+ */
+ if (want_write && local_hdb_read_only && primary_server_URI)
+ return KADM5_READ_ONLY;
+ if (want_write && read_only)
+ return KADM5_READ_ONLY;
+
+ /*
+ * Configure kadm5 connection.
+ *
+ * Note that all of these are optional, and will be found in krb5.conf or,
+ * in some cases, in DNS, as needed.
+ */
+ memset(&conf, 0, sizeof(conf));
+ conf.realm = NULL;
+ conf.dbname = NULL;
+ conf.stash_file = NULL;
+ conf.admin_server = NULL;
+ conf.readonly_admin_server = NULL;
+ set_conf(conf, realm, want_realm, KADM5_CONFIG_REALM);
+ set_conf(conf, dbname, hdb, KADM5_CONFIG_DBNAME);
+ set_conf(conf, stash_file, stash_file, KADM5_CONFIG_STASH_FILE);
+
+ /*
+ * If we have a local HDB we'll use it if we can. If the local HDB is
+ * read-only and the caller wants to write, then we won't use the local
+ * HDB, naturally.
+ */
+ if (local_hdb && (!local_hdb_read_only || !want_write)) {
+ ret = kadm5_s_init_with_password_ctx(context,
+ kadmin_client_name,
+ NULL, /* password */
+ NULL, /* service_name */
+ &conf,
+ 0, /* struct_version */
+ 0, /* api_version */
+ kadm_handle);
+ goto out;
+ }
+
+ /*
+ * Remote connection. This will connect to a read-only kadmind if
+ * possible, and if so, reconnect to a writable kadmind as needed.
+ *
+ * Note that kadmin_client_keytab can be an HDB: or HDBGET: keytab.
+ */
+ if (writable_kadmin_server)
+ set_conf(conf, admin_server, writable_kadmin_server, KADM5_CONFIG_ADMIN_SERVER);
+ if (kadmin_server)
+ set_conf(conf, readonly_admin_server, kadmin_server,
+ KADM5_CONFIG_READONLY_ADMIN_SERVER);
+ ret = kadm5_c_init_with_skey_ctx(context,
+ kadmin_client_name,
+ kadmin_client_keytab,
+ KADM5_ADMIN_SERVICE,
+ &conf,
+ 0, /* struct_version */
+ 0, /* api_version */
+ kadm_handle);
+ goto out;
+
+enomem:
+ ret = krb5_enomem(context);
+
+out:
+ free(conf.readonly_admin_server);
+ free(conf.admin_server);
+ free(conf.stash_file);
+ free(conf.dbname);
+ free(conf.realm);
+ return ret;
+}
+
+static krb5_error_code resp(kadmin_request_desc, int, krb5_error_code,
+ enum MHD_ResponseMemoryMode, const char *,
+ const void *, size_t, const char *);
+static krb5_error_code bad_req(kadmin_request_desc, krb5_error_code, int,
+ const char *, ...)
+ HEIMDAL_PRINTF_ATTRIBUTE((__printf__, 4, 5));
+
+static krb5_error_code bad_enomem(kadmin_request_desc, krb5_error_code);
+static krb5_error_code bad_400(kadmin_request_desc, krb5_error_code, const char *);
+static krb5_error_code bad_401(kadmin_request_desc, const char *);
+static krb5_error_code bad_403(kadmin_request_desc, krb5_error_code, const char *);
+static krb5_error_code bad_404(kadmin_request_desc, krb5_error_code, const char *);
+static krb5_error_code bad_405(kadmin_request_desc, const char *);
+/*static krb5_error_code bad_500(kadmin_request_desc, krb5_error_code, const char *);*/
+static krb5_error_code bad_503(kadmin_request_desc, krb5_error_code, const char *);
+
+static int
+validate_token(kadmin_request_desc r)
+{
+ krb5_error_code ret;
+ const char *token;
+ const char *host;
+ char token_type[64]; /* Plenty */
+ char *p;
+ krb5_data tok;
+ size_t host_len, brk, i;
+
+ memset(&r->token_times, 0, sizeof(r->token_times));
+ host = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_HOST);
+ if (host == NULL)
+ return bad_400(r, EINVAL, "Host header is missing");
+
+ /* Exclude port number here (IPv6-safe because of the below) */
+ host_len = ((p = strchr(host, ':'))) ? p - host : strlen(host);
+
+ token = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_AUTHORIZATION);
+ if (token == NULL)
+ return bad_401(r, "Authorization token is missing");
+ brk = strcspn(token, " \t");
+ if (token[brk] == '\0' || brk > sizeof(token_type) - 1)
+ return bad_401(r, "Authorization token is missing");
+ memcpy(token_type, token, brk);
+ token_type[brk] = '\0';
+ token += brk + 1;
+ tok.length = strlen(token);
+ tok.data = (void *)(uintptr_t)token;
+
+ for (i = 0; i < audiences.num_strings; i++)
+ if (strncasecmp(host, audiences.strings[i], host_len) == 0 &&
+ audiences.strings[i][host_len] == '\0')
+ break;
+ if (i == audiences.num_strings)
+ return bad_403(r, EINVAL, "Host: value is not accepted here");
+
+ r->sname = strdup(host); /* No need to check for ENOMEM here */
+
+ ret = kdc_validate_token(r->context, NULL /* realm */, token_type, &tok,
+ (const char **)&audiences.strings[i], 1,
+ &r->cprinc, &r->token_times);
+ if (ret)
+ return bad_403(r, ret, "Token validation failed");
+ if (r->cprinc == NULL)
+ return bad_403(r, ret,
+ "Could not extract a principal name from token");
+ ret = krb5_unparse_name(r->context, r->cprinc, &r->cname);
+ if (ret)
+ return bad_503(r, ret,
+ "Could not extract a principal name from token");
+ return 0;
+}
+
+static void
+k5_free_context(void *ctx)
+{
+ krb5_free_context(ctx);
+}
+
+#ifndef HAVE_UNLINKAT
+static int
+unlink1file(const char *dname, const char *name)
+{
+ char p[PATH_MAX];
+
+ if (strlcpy(p, dname, sizeof(p)) < sizeof(p) &&
+ strlcat(p, "/", sizeof(p)) < sizeof(p) &&
+ strlcat(p, name, sizeof(p)) < sizeof(p))
+ return unlink(p);
+ return ERANGE;
+}
+#endif
+
+static void
+rm_cache_dir(void)
+{
+ struct dirent *e;
+ DIR *d;
+
+ /*
+ * This works, but not on Win32:
+ *
+ * (void) simple_execlp("rm", "rm", "-rf", cache_dir, NULL);
+ *
+ * We make no directories in `cache_dir', so we need not recurse.
+ */
+ if ((d = opendir(cache_dir)) == NULL)
+ return;
+
+ while ((e = readdir(d))) {
+#ifdef HAVE_UNLINKAT
+ /*
+ * Because unlinkat() takes a directory FD, implementing one for
+ * libroken is tricky at best. Instead we might want to implement an
+ * rm_dash_rf() function in lib/roken.
+ */
+ (void) unlinkat(dirfd(d), e->d_name, 0);
+#else
+ (void) unlink1file(cache_dir, e->d_name);
+#endif
+ }
+ (void) closedir(d);
+ (void) rmdir(cache_dir);
+}
+
+/*
+ * Work around older libmicrohttpd not strduping response header values when
+ * set.
+ */
+static HEIMDAL_THREAD_LOCAL struct redirect_uri {
+ char uri[4096];
+ size_t len;
+ size_t first_param;
+ int valid;
+} redirect_uri;
+
+static void
+redirect_uri_appends(struct redirect_uri *redirect,
+ const char *s)
+{
+ size_t sz, len;
+ char *p;
+
+ if (!redirect->valid || redirect->len >= sizeof(redirect->uri) - 1) {
+ redirect->valid = 0;
+ return;
+ }
+ /* Optimize strlcpy by using redirect->uri + redirect->len */
+ p = redirect->uri + redirect->len;
+ sz = sizeof(redirect->uri) - redirect->len;
+ if ((len = strlcpy(p, s, sz)) >= sz)
+ redirect->valid = 0;
+ else
+ redirect->len += len;
+}
+
+static heim_mhd_result
+make_redirect_uri_param_cb(void *d,
+ enum MHD_ValueKind kind,
+ const char *key,
+ const char *val)
+{
+ struct redirect_uri *redirect = d;
+
+ redirect_uri_appends(redirect, redirect->first_param ? "?" : "&");
+ redirect_uri_appends(redirect, key);
+ if (val) {
+ redirect_uri_appends(redirect, "=");
+ redirect_uri_appends(redirect, val);
+ }
+ redirect->first_param = 0;
+ return MHD_YES;
+}
+
+static const char *
+make_redirect_uri(kadmin_request_desc r, const char *base)
+{
+ redirect_uri.len = 0;
+ redirect_uri.uri[0] = '\0';
+ redirect_uri.valid = 1;
+ redirect_uri.first_param = 1;
+
+ redirect_uri_appends(&redirect_uri, base); /* Redirect to primary URI base */
+ redirect_uri_appends(&redirect_uri, r->reqtype); /* URI local-part */
+ (void) MHD_get_connection_values(r->connection, MHD_GET_ARGUMENT_KIND,
+ make_redirect_uri_param_cb,
+ &redirect_uri);
+ return redirect_uri.valid ? redirect_uri.uri : NULL;
+}
+
+
+/*
+ * XXX Shouldn't be a body, but a status message. The body should be
+ * configurable to be from a file. MHD doesn't give us a way to set the
+ * response status message though, just the body.
+ *
+ * Calls audit_trail().
+ *
+ * Returns -1 if something terrible happened, which should ultimately cause
+ * route() to return MHD_NO, which should cause libmicrohttpd to close the
+ * connection to the user-agent.
+ *
+ * Returns 0 in all other cases.
+ */
+static krb5_error_code
+resp(kadmin_request_desc r,
+ int http_status_code,
+ krb5_error_code ret,
+ enum MHD_ResponseMemoryMode rmmode,
+ const char *content_type,
+ const void *body,
+ size_t bodylen,
+ const char *token)
+{
+ struct MHD_Response *response;
+ int mret = MHD_YES;
+
+ if (r->response_set) {
+ krb5_log_msg(r->context, logfac, 1, NULL,
+ "Internal error; attempted to set a second response");
+ return 0;
+ }
+
+ (void) gettimeofday(&r->tv_end, NULL);
+ audit_trail(r, ret);
+
+ if (body && bodylen == BODYLEN_IS_STRLEN)
+ bodylen = strlen(body);
+
+ response = MHD_create_response_from_buffer(bodylen, rk_UNCONST(body),
+ rmmode);
+ if (response == NULL)
+ return -1;
+ mret = MHD_add_response_header(response, MHD_HTTP_HEADER_AGE, "0");
+ if (mret == MHD_YES && http_status_code == MHD_HTTP_OK) {
+ krb5_timestamp now;
+
+ free(r->cache_control);
+ r->cache_control = NULL;
+ krb5_timeofday(r->context, &now);
+ if (r->pw_end && r->pw_end > now) {
+ if (asprintf(&r->cache_control, "no-store, max-age=%lld",
+ (long long)r->pw_end - now) == -1 ||
+ r->cache_control == NULL)
+ /* Soft handling of ENOMEM here */
+ mret = MHD_add_response_header(response,
+ MHD_HTTP_HEADER_CACHE_CONTROL,
+ "no-store, max-age=3600");
+ else
+ mret = MHD_add_response_header(response,
+ MHD_HTTP_HEADER_CACHE_CONTROL,
+ r->cache_control);
+
+ } else
+ mret = MHD_add_response_header(response,
+ MHD_HTTP_HEADER_CACHE_CONTROL,
+ "no-store, max-age=0");
+ } else {
+ /* Shouldn't happen */
+ mret = MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL,
+ "no-store, max-age=0");
+ }
+ if (mret == MHD_YES && http_status_code == MHD_HTTP_UNAUTHORIZED) {
+ size_t i;
+
+ if (auth_types.num_strings < 1)
+ http_status_code = MHD_HTTP_SERVICE_UNAVAILABLE;
+ else
+ for (i = 0; mret == MHD_YES && i < auth_types.num_strings; i++)
+ mret = MHD_add_response_header(response,
+ MHD_HTTP_HEADER_WWW_AUTHENTICATE,
+ auth_types.strings[i]);
+ } else if (mret == MHD_YES && http_status_code == MHD_HTTP_TEMPORARY_REDIRECT) {
+ const char *redir = make_redirect_uri(r, primary_server_URI);
+
+ if (redir)
+ mret = MHD_add_response_header(response, MHD_HTTP_HEADER_LOCATION,
+ redir);
+ else
+ /* XXX Find a way to set a new response body; log */
+ http_status_code = MHD_HTTP_SERVICE_UNAVAILABLE;
+ }
+
+ if (mret == MHD_YES && r->csrf_token)
+ mret = MHD_add_response_header(response,
+ "X-CSRF-Token",
+ r->csrf_token);
+
+ if (mret == MHD_YES && content_type) {
+ mret = MHD_add_response_header(response,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ content_type);
+ }
+ if (mret != MHD_NO)
+ mret = MHD_queue_response(r->connection, http_status_code, response);
+ MHD_destroy_response(response);
+ r->response_set = 1;
+ return mret == MHD_NO ? -1 : 0;
+}
+
+static krb5_error_code
+bad_reqv(kadmin_request_desc r,
+ krb5_error_code code,
+ int http_status_code,
+ const char *fmt,
+ va_list ap)
+{
+ krb5_error_code ret;
+ krb5_context context = NULL;
+ const char *k5msg = NULL;
+ const char *emsg = NULL;
+ char *formatted = NULL;
+ char *msg = NULL;
+
+ context = r->context;
+ if (r->hcontext && r->kv)
+ heim_audit_setkv_number((heim_svc_req_desc)r, "http-status-code",
+ http_status_code);
+ (void) gettimeofday(&r->tv_end, NULL);
+ if (code == ENOMEM) {
+ if (context)
+ krb5_log_msg(context, logfac, 1, NULL, "Out of memory");
+ return resp(r, http_status_code, code, MHD_RESPMEM_PERSISTENT,
+ NULL, fmt, BODYLEN_IS_STRLEN, NULL);
+ }
+
+ if (code) {
+ if (context)
+ emsg = k5msg = krb5_get_error_message(context, code);
+ else
+ emsg = strerror(code);
+ }
+
+ ret = vasprintf(&formatted, fmt, ap) == -1;
+ if (code) {
+ if (ret > -1 && formatted)
+ ret = asprintf(&msg, "%s: %s (%d)", formatted, emsg, (int)code);
+ } else {
+ msg = formatted;
+ formatted = NULL;
+ }
+ if (r->hcontext)
+ heim_audit_addreason((heim_svc_req_desc)r, "%s", formatted);
+ krb5_free_error_message(context, k5msg);
+
+ if (ret == -1 || msg == NULL) {
+ if (context)
+ krb5_log_msg(context, logfac, 1, NULL, "Out of memory");
+ return resp(r, MHD_HTTP_SERVICE_UNAVAILABLE, ENOMEM,
+ MHD_RESPMEM_PERSISTENT, NULL,
+ "Out of memory", BODYLEN_IS_STRLEN, NULL);
+ }
+
+ ret = resp(r, http_status_code, code, MHD_RESPMEM_MUST_COPY,
+ NULL, msg, BODYLEN_IS_STRLEN, NULL);
+ free(formatted);
+ free(msg);
+ return ret == -1 ? -1 : code;
+}
+
+static krb5_error_code
+bad_req(kadmin_request_desc r,
+ krb5_error_code code,
+ int http_status_code,
+ const char *fmt,
+ ...)
+{
+ krb5_error_code ret;
+ va_list ap;
+
+ va_start(ap, fmt);
+ ret = bad_reqv(r, code, http_status_code, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+static krb5_error_code
+bad_enomem(kadmin_request_desc r, krb5_error_code ret)
+{
+ return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
+ "Out of memory");
+}
+
+static krb5_error_code
+bad_400(kadmin_request_desc r, int ret, const char *reason)
+{
+ return bad_req(r, ret, MHD_HTTP_BAD_REQUEST, "%s", reason);
+}
+
+static krb5_error_code
+bad_401(kadmin_request_desc r, const char *reason)
+{
+ return bad_req(r, EACCES, MHD_HTTP_UNAUTHORIZED, "%s", reason);
+}
+
+static krb5_error_code
+bad_403(kadmin_request_desc r, krb5_error_code ret, const char *reason)
+{
+ return bad_req(r, ret, MHD_HTTP_FORBIDDEN, "%s", reason);
+}
+
+static krb5_error_code
+bad_404(kadmin_request_desc r, krb5_error_code ret, const char *name)
+{
+ return bad_req(r, ret, MHD_HTTP_NOT_FOUND,
+ "Resource not found: %s", name);
+}
+
+static krb5_error_code
+bad_405(kadmin_request_desc r, const char *method)
+{
+ return bad_req(r, EPERM, MHD_HTTP_METHOD_NOT_ALLOWED,
+ "Method not supported: %s", method);
+}
+
+static krb5_error_code
+bad_413(kadmin_request_desc r)
+{
+ return bad_req(r, E2BIG, MHD_HTTP_METHOD_NOT_ALLOWED,
+ "POST request body too large");
+}
+
+static krb5_error_code
+bad_method_want_POST(kadmin_request_desc r)
+{
+ return bad_req(r, EPERM, MHD_HTTP_METHOD_NOT_ALLOWED,
+ "Use POST for making changes to principals");
+}
+
+#if 0
+static krb5_error_code
+bad_500(kadmin_request_desc r,
+ krb5_error_code ret,
+ const char *reason)
+{
+ return bad_req(r, ret, MHD_HTTP_INTERNAL_SERVER_ERROR,
+ "Internal error: %s", reason);
+}
+#endif
+
+static krb5_error_code
+bad_503(kadmin_request_desc r,
+ krb5_error_code ret,
+ const char *reason)
+{
+ return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
+ "Service unavailable: %s", reason);
+}
+
+static krb5_error_code
+good_ext_keytab(kadmin_request_desc r)
+{
+ krb5_error_code ret;
+ size_t bodylen;
+ void *body;
+ char *p;
+
+ if (!r->keytab_name || !(p = strchr(r->keytab_name, ':')))
+ return bad_503(r, EINVAL, "Internal error (no keytab produced)");
+ p++;
+ if (strncmp(p, cache_dir, strlen(cache_dir)) != 0)
+ return bad_503(r, EINVAL, "Internal error");
+ ret = rk_undumpdata(p, &body, &bodylen);
+ if (ret)
+ return bad_503(r, ret, "Could not recover keytab from temp file");
+
+ ret = resp(r, MHD_HTTP_OK, 0, MHD_RESPMEM_MUST_COPY,
+ "application/octet-stream", body, bodylen, NULL);
+ free(body);
+ return ret;
+}
+
+static krb5_error_code
+check_service_name(kadmin_request_desc r, const char *name)
+{
+ if (name == NULL || name[0] == '\0' ||
+ strchr(name, '/') || strchr(name, '\\') || strchr(name, '@') ||
+ strcmp(name, "krbtgt") == 0 ||
+ strcmp(name, "iprop") == 0 ||
+ strcmp(name, "kadmin") == 0 ||
+ strcmp(name, "hprop") == 0 ||
+ strcmp(name, "WELLKNOWN") == 0 ||
+ strcmp(name, "K") == 0) {
+ krb5_set_error_message(r->context, EACCES,
+ "No one is allowed to fetch keys for "
+ "Heimdal service %s", name);
+ return EACCES;
+ }
+ if (strcmp(name, "root") != 0 &&
+ strcmp(name, "host") != 0 &&
+ strcmp(name, "exceed") != 0)
+ return 0;
+ if (krb5_config_get_bool_default(r->context, NULL, FALSE,
+ "ext_keytab",
+ "csr_authorizer_handles_svc_names",
+ NULL))
+ return 0;
+ krb5_set_error_message(r->context, EACCES,
+ "No one is allowed to fetch keys for "
+ "service \"%s\" because of authorizer "
+ "limitations", name);
+ return EACCES;
+}
+
+static heim_mhd_result
+param_cb(void *d,
+ enum MHD_ValueKind kind,
+ const char *key,
+ const char *val)
+{
+ kadmin_request_desc r = d;
+ krb5_error_code ret = 0;
+ heim_string_t s = NULL;
+
+ /*
+ * Multi-valued params:
+ *
+ * - spn=<service>/<hostname>
+ * - dNSName=<hostname>
+ * - service=<service>
+ *
+ * Single-valued params:
+ *
+ * - realm=<REALM>
+ * - materialize=true -- create a concrete princ where it's virtual
+ * - enctypes=... -- key-salt types
+ * - revoke=true -- delete old keys (concrete princs only)
+ * - rotate=true -- change keys (no-op for virtual princs)
+ * - create=true -- create a concrete princ
+ * - ro=true -- perform no writes
+ */
+
+ if (strcmp(key, "realm") == 0 && val) {
+ if (!r->realm && !(r->realm = strdup(val)))
+ ret = krb5_enomem(r->context);
+ } else if (strcmp(key, "materialize") == 0 ||
+ strcmp(key, "revoke") == 0 ||
+ strcmp(key, "rotate") == 0 ||
+ strcmp(key, "create") == 0 ||
+ strcmp(key, "ro") == 0) {
+ heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
+ "requested_option", "%s", key);
+ if (!val || strcmp(val, "true") != 0)
+ krb5_set_error_message(r->context, ret = EINVAL,
+ "get-keys \"%s\" q-param accepts "
+ "only \"true\"", key);
+ else if (strcmp(key, "materialize") == 0)
+ r->materialize = 1;
+ else if (strcmp(key, "revoke") == 0)
+ r->revoke = 1;
+ else if (strcmp(key, "rotate") == 0)
+ r->rotate = 1;
+ else if (strcmp(key, "create") == 0)
+ r->create = 1;
+ else if (strcmp(key, "ro") == 0)
+ r->ro = 1;
+ } else if (strcmp(key, "dNSName") == 0 && val) {
+ heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
+ "requested_dNSName", "%s", val);
+ if (r->is_self) {
+ krb5_set_error_message(r->context, ret = EACCES,
+ "only one service may be requested for self");
+ } else if (strchr(val, '.') == NULL) {
+ krb5_set_error_message(r->context, ret = EACCES,
+ "dNSName must have at least one '.' in it");
+ } else {
+ s = heim_string_create(val);
+ if (!s)
+ ret = krb5_enomem(r->context);
+ else
+ ret = heim_array_append_value(r->hostnames, s);
+ }
+ if (ret == 0)
+ ret = hx509_request_add_dns_name(r->context->hx509ctx, r->req, val);
+ } else if (strcmp(key, "service") == 0 && val) {
+ heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
+ "requested_service", "%s", val);
+ if (r->is_self)
+ krb5_set_error_message(r->context, ret = EACCES,
+ "use \"spn\" for self");
+ else
+ ret = check_service_name(r, val);
+ if (ret == 0) {
+ s = heim_string_create(val);
+ if (!s)
+ ret = krb5_enomem(r->context);
+ else
+ ret = heim_array_append_value(r->service_names, s);
+ }
+ } else if (strcmp(key, "enctypes") == 0 && val) {
+ r->enctypes = strdup(val);
+ if (!(r->enctypes = strdup(val)))
+ ret = krb5_enomem(r->context);
+ heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
+ "requested_enctypes", "%s", val);
+ } else if (r->is_self && strcmp(key, "spn") == 0 && val) {
+ heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
+ "requested_spn", "%s", val);
+ krb5_set_error_message(r->context, ret = EACCES,
+ "only one service may be requested for self");
+ } else if (strcmp(key, "spn") == 0 && val) {
+ krb5_principal p = NULL;
+ const char *hostname = "";
+
+ heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
+ "requested_spn", "%s", val);
+
+ ret = krb5_parse_name_flags(r->context, val,
+ KRB5_PRINCIPAL_PARSE_NO_DEF_REALM, &p);
+ if (ret == 0 && krb5_principal_get_realm(r->context, p) == NULL)
+ ret = krb5_principal_set_realm(r->context, p,
+ r->realm ? r->realm : realm);
+
+ /*
+ * The SPN has to have two components.
+ *
+ * TODO: Support more components? Support AD-style NetBIOS computer
+ * account names?
+ */
+ if (ret == 0 && krb5_principal_get_num_comp(r->context, p) != 2)
+ ret = ENOTSUP;
+
+ /*
+ * Allow only certain service names. Except that when
+ * the SPN == the requestor's principal name then allow the "host"
+ * service name.
+ */
+ if (ret == 0) {
+ const char *service =
+ krb5_principal_get_comp_string(r->context, p, 0);
+
+ if (strcmp(service, "host") == 0 &&
+ krb5_principal_compare(r->context, p, r->cprinc) &&
+ !r->is_self &&
+ heim_array_get_length(r->hostnames) == 0 &&
+ heim_array_get_length(r->spns) == 0) {
+ r->is_self = 1;
+ } else
+ ret = check_service_name(r, service);
+ }
+ if (ret == 0 && !krb5_principal_compare(r->context, p, r->cprinc))
+ ret = check_service_name(r,
+ krb5_principal_get_comp_string(r->context,
+ p, 0));
+ if (ret == 0) {
+ hostname = krb5_principal_get_comp_string(r->context, p, 1);
+ if (!hostname || !strchr(hostname, '.'))
+ krb5_set_error_message(r->context, ret = ENOTSUP,
+ "Only host-based service names supported");
+ }
+ if (ret == 0 && r->realm)
+ ret = krb5_principal_set_realm(r->context, p, r->realm);
+ else if (ret == 0 && realm)
+ ret = krb5_principal_set_realm(r->context, p, realm);
+ if (ret == 0)
+ ret = hx509_request_add_dns_name(r->context->hx509ctx, r->req,
+ hostname);
+ if (ret == 0 && !(s = heim_string_create(val)))
+ ret = krb5_enomem(r->context);
+ if (ret == 0)
+ ret = heim_array_append_value(r->spns, s);
+ krb5_free_principal(r->context, p);
+
+#if 0
+ /* The authorizer probably doesn't know what to do with this */
+ ret = hx509_request_add_pkinit(r->context->hx509ctx, r->req, val);
+#endif
+ } else {
+ /* Produce error for unknown params */
+ heim_audit_setkv_bool((heim_svc_req_desc)r, "requested_unknown", TRUE);
+ krb5_set_error_message(r->context, ret = ENOTSUP,
+ "Query parameter %s not supported", key);
+ }
+ if (ret && !r->error_code)
+ r->error_code = ret;
+ heim_release(s);
+ return ret ? MHD_NO /* Stop iterating */ : MHD_YES;
+}
+
+static krb5_error_code
+authorize_req(kadmin_request_desc r)
+{
+ krb5_error_code ret;
+
+ r->is_self = 0;
+ ret = hx509_request_init(r->context->hx509ctx, &r->req);
+ if (ret)
+ return bad_enomem(r, ret);
+ (void) MHD_get_connection_values(r->connection, MHD_GET_ARGUMENT_KIND,
+ param_cb, r);
+ ret = r->error_code;
+ if (ret == EACCES)
+ return bad_403(r, ret, "Not authorized to requested principal(s)");
+ if (ret)
+ return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
+ "Could not handle query parameters");
+ if (r->is_self)
+ ret = 0;
+ else
+ ret = kdc_authorize_csr(r->context, "ext_keytab", r->req, r->cprinc);
+ if (ret == EACCES || ret == EINVAL || ret == ENOTSUP ||
+ ret == KRB5KDC_ERR_POLICY)
+ return bad_403(r, ret, "Not authorized to requested principal(s)");
+ if (ret)
+ return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
+ "Error checking authorization");
+ return ret;
+}
+
+static krb5_error_code
+make_keytab(kadmin_request_desc r)
+{
+ krb5_error_code ret = 0;
+ int fd = -1;
+
+ r->keytab_name = NULL;
+ if (asprintf(&r->keytab_name, "FILE:%s/kt-XXXXXX", cache_dir) == -1 ||
+ r->keytab_name == NULL)
+ ret = krb5_enomem(r->context);
+ if (ret == 0)
+ fd = mkstemp(r->keytab_name + sizeof("FILE:") - 1);
+ if (ret == 0 && fd == -1)
+ ret = errno;
+ if (ret == 0)
+ ret = krb5_kt_resolve(r->context, r->keytab_name, &r->keytab);
+ if (fd != -1)
+ (void) close(fd);
+ return ret;
+}
+
+static krb5_error_code
+write_keytab(kadmin_request_desc r,
+ kadm5_principal_ent_rec *princ,
+ const char *unparsed)
+{
+ krb5_error_code ret = 0;
+ krb5_keytab_entry key;
+ size_t i;
+
+ if (princ->n_key_data <= 0)
+ return 0;
+
+ if (kadm5_some_keys_are_bogus(princ->n_key_data, &princ->key_data[0])) {
+ krb5_warn(r->context, ret,
+ "httpkadmind running with insufficient kadmin privilege "
+ "for extracting keys for %s", unparsed);
+ krb5_log_msg(r->context, logfac, 1, NULL,
+ "httpkadmind running with insufficient kadmin privilege "
+ "for extracting keys for %s", unparsed);
+ return EACCES;
+ }
+
+ memset(&key, 0, sizeof(key));
+ for (i = 0; ret == 0 && i < princ->n_key_data; i++) {
+ krb5_key_data *kd = &princ->key_data[i];
+
+ key.principal = princ->principal;
+ key.vno = kd->key_data_kvno;
+ key.keyblock.keytype = kd->key_data_type[0];
+ key.keyblock.keyvalue.length = kd->key_data_length[0];
+ key.keyblock.keyvalue.data = kd->key_data_contents[0];
+
+ /*
+ * FIXME kadm5 doesn't give us set_time here. If it gave us the
+ * KeyRotation metadata, we could compute it. But this might be a
+ * concrete principal with concrete keys, in which case we can't.
+ *
+ * To fix this we need to extend the protocol and the API.
+ */
+ key.timestamp = time(NULL);
+
+ ret = krb5_kt_add_entry(r->context, r->keytab, &key);
+ }
+ if (ret)
+ krb5_warn(r->context, ret,
+ "Failed to write keytab entries for %s", unparsed);
+
+ return ret;
+}
+
+static void
+random_password(krb5_context context, char *buf, size_t buflen)
+{
+ static const char chars[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,";
+ char p[32];
+ size_t i;
+ char b;
+
+ buflen--;
+ for (i = 0; i < buflen; i++) {
+ if (i % sizeof(p) == 0)
+ krb5_generate_random_block(p, sizeof(p));
+ b = p[i % sizeof(p)];
+ buf[i] = chars[b % (sizeof(chars) - 1)];
+ }
+ buf[i] = '\0';
+}
+
+static krb5_error_code
+make_kstuple(krb5_context context,
+ kadm5_principal_ent_rec *p,
+ krb5_key_salt_tuple **kstuple,
+ size_t *n_kstuple)
+{
+ size_t i;
+
+ *kstuple = 0;
+ *n_kstuple = 0;
+
+ if (p->n_key_data < 1)
+ return 0;
+ *kstuple = calloc(p->n_key_data, sizeof (**kstuple));
+ for (i = 0; *kstuple && i < p->n_key_data; i++) {
+ if (p->key_data[i].key_data_kvno == p->kvno) {
+ (*kstuple)[i].ks_enctype = p->key_data[i].key_data_type[0];
+ (*kstuple)[i].ks_salttype = p->key_data[i].key_data_type[1];
+ (*n_kstuple)++;
+ }
+ }
+ return *kstuple ? 0 :krb5_enomem(context);
+}
+
+/* Copied from kadmin/util.c */
+struct units kdb_attrs[] = {
+ { "auth-data-reqd", KRB5_KDB_AUTH_DATA_REQUIRED },
+ { "no-auth-data-reqd", KRB5_KDB_NO_AUTH_DATA_REQUIRED },
+ { "disallow-client", KRB5_KDB_DISALLOW_CLIENT },
+ { "virtual", KRB5_KDB_VIRTUAL },
+ { "virtual-keys", KRB5_KDB_VIRTUAL_KEYS },
+ { "allow-digest", KRB5_KDB_ALLOW_DIGEST },
+ { "allow-kerberos4", KRB5_KDB_ALLOW_KERBEROS4 },
+ { "trusted-for-delegation", KRB5_KDB_TRUSTED_FOR_DELEGATION },
+ { "ok-as-delegate", KRB5_KDB_OK_AS_DELEGATE },
+ { "new-princ", KRB5_KDB_NEW_PRINC },
+ { "support-desmd5", KRB5_KDB_SUPPORT_DESMD5 },
+ { "pwchange-service", KRB5_KDB_PWCHANGE_SERVICE },
+ { "disallow-svr", KRB5_KDB_DISALLOW_SVR },
+ { "requires-pw-change", KRB5_KDB_REQUIRES_PWCHANGE },
+ { "requires-hw-auth", KRB5_KDB_REQUIRES_HW_AUTH },
+ { "requires-pre-auth", KRB5_KDB_REQUIRES_PRE_AUTH },
+ { "disallow-all-tix", KRB5_KDB_DISALLOW_ALL_TIX },
+ { "disallow-dup-skey", KRB5_KDB_DISALLOW_DUP_SKEY },
+ { "disallow-proxiable", KRB5_KDB_DISALLOW_PROXIABLE },
+ { "disallow-renewable", KRB5_KDB_DISALLOW_RENEWABLE },
+ { "disallow-tgt-based", KRB5_KDB_DISALLOW_TGT_BASED },
+ { "disallow-forwardable", KRB5_KDB_DISALLOW_FORWARDABLE },
+ { "disallow-postdated", KRB5_KDB_DISALLOW_POSTDATED },
+ { NULL, 0 }
+};
+
+/*
+ * Determine the default/allowed attributes for some new principal.
+ */
+static krb5_flags
+create_attributes(kadmin_request_desc r, krb5_const_principal p)
+{
+ krb5_error_code ret;
+ const char *srealm = krb5_principal_get_realm(r->context, p);
+ const char *svc;
+ const char *hn;
+
+ /* Has to be a host-based service principal (for now) */
+ if (krb5_principal_get_num_comp(r->context, p) != 2)
+ return 0;
+
+ hn = krb5_principal_get_comp_string(r->context, p, 1);
+ svc = krb5_principal_get_comp_string(r->context, p, 0);
+
+ while (hn && strchr(hn, '.') != NULL) {
+ kadm5_principal_ent_rec nsprinc;
+ krb5_principal nsp;
+ uint64_t a = 0;
+ const char *as;
+
+ /* Try finding a virtual host-based service principal namespace */
+ memset(&nsprinc, 0, sizeof(nsprinc));
+ ret = krb5_make_principal(r->context, &nsp, srealm,
+ KRB5_WELLKNOWN_NAME, HDB_WK_NAMESPACE,
+ svc, hn, NULL);
+ if (ret == 0)
+ ret = kadm5_get_principal(r->kadm_handle, nsp, &nsprinc,
+ KADM5_PRINCIPAL | KADM5_ATTRIBUTES);
+ krb5_free_principal(r->context, nsp);
+ if (ret == 0) {
+ /* Found one; use it even if disabled, but drop that attribute */
+ a = nsprinc.attributes & ~KRB5_KDB_DISALLOW_ALL_TIX;
+ kadm5_free_principal_ent(r->kadm_handle, &nsprinc);
+ return a;
+ }
+
+ /* Fallback on krb5.conf */
+ as = krb5_config_get_string(r->context, NULL, "ext_keytab",
+ "new_hostbased_service_principal_attributes",
+ svc, hn, NULL);
+ if (as) {
+ a = parse_flags(as, kdb_attrs, 0);
+ if (a == (uint64_t)-1) {
+ krb5_warnx(r->context, "Invalid value for [ext_keytab] "
+ "new_hostbased_service_principal_attributes");
+ return 0;
+ }
+ return a;
+ }
+
+ hn = strchr(hn + 1, '.');
+ }
+
+ return 0;
+}
+
+/*
+ * Get keys for one principal.
+ *
+ * Does NOT set an HTTP response.
+ */
+static krb5_error_code
+get_keys1(kadmin_request_desc r, const char *pname)
+{
+ kadm5_principal_ent_rec princ;
+ krb5_key_salt_tuple *kstuple = NULL;
+ krb5_error_code ret = 0;
+ krb5_principal p = NULL;
+ uint32_t mask =
+ KADM5_PRINCIPAL | KADM5_KVNO | KADM5_MAX_LIFE | KADM5_MAX_RLIFE |
+ KADM5_PW_EXPIRATION | KADM5_ATTRIBUTES | KADM5_KEY_DATA |
+ KADM5_TL_DATA;
+ uint32_t create_mask = mask & ~(KADM5_KEY_DATA | KADM5_TL_DATA);
+ size_t nkstuple = 0;
+ int change = 0;
+ int refetch = 0;
+ int freeit = 0;
+
+ memset(&princ, 0, sizeof(princ));
+ princ.key_data = NULL;
+ princ.tl_data = NULL;
+
+ ret = krb5_parse_name(r->context, pname, &p);
+ if (ret == 0 && r->realm)
+ ret = krb5_principal_set_realm(r->context, p, r->realm);
+ else if (ret == 0 && realm)
+ ret = krb5_principal_set_realm(r->context, p, realm);
+ if (ret == 0 && r->enctypes)
+ ret = krb5_string_to_keysalts2(r->context, r->enctypes,
+ &nkstuple, &kstuple);
+ if (ret == 0)
+ ret = kadm5_get_principal(r->kadm_handle, p, &princ, mask);
+ if (ret == 0) {
+ freeit = 1;
+
+ /*
+ * If princ is virtual and we're not asked to materialize, ignore
+ * requests to rotate.
+ */
+ if (!r->materialize &&
+ (princ.attributes & (KRB5_KDB_VIRTUAL_KEYS | KRB5_KDB_VIRTUAL))) {
+ r->rotate = 0;
+ r->revoke = 0;
+ }
+ }
+
+ change = !r->ro && (r->rotate || r->revoke);
+
+ /* Handle create / materialize options */
+ if (ret == KADM5_UNK_PRINC && r->create) {
+ char pw[128];
+
+ memset(&princ, 0, sizeof(princ));
+ princ.attributes = create_attributes(r, p);
+
+ if (read_only)
+ ret = KADM5_READ_ONLY;
+ else
+ ret = strcmp(r->method, "POST") == 0 ? 0 : ENOSYS; /* XXX */
+ if (ret == 0 && local_hdb && local_hdb_read_only) {
+ /* Make sure we can write */
+ kadm5_destroy(r->kadm_handle);
+ r->kadm_handle = NULL;
+ ret = get_kadm_handle(r->context, r->realm, 1 /* want_write */,
+ &r->kadm_handle);
+ }
+ /*
+ * Some software is allergic to kvno 1, assuming that kvno 1 implies
+ * half-baked service principal. We've some vague recollection of
+ * something similar for kvno 2, so let's start at 3.
+ */
+ princ.kvno = 3;
+ princ.tl_data = NULL;
+ princ.key_data = NULL;
+ princ.max_life = 24 * 3600; /* XXX Make configurable */
+ princ.max_renewable_life = princ.max_life; /* XXX Make configurable */
+
+ random_password(r->context, pw, sizeof(pw));
+ princ.principal = p; /* Borrow */
+ if (ret == 0)
+ ret = kadm5_create_principal_3(r->kadm_handle, &princ, create_mask,
+ nkstuple, kstuple, pw);
+ princ.principal = NULL; /* Return */
+ refetch = 1;
+ freeit = 1;
+ } else if (ret == 0 && r->materialize &&
+ (princ.attributes & KRB5_KDB_VIRTUAL)) {
+
+ if (read_only)
+ ret = KADM5_READ_ONLY;
+ else
+ ret = strcmp(r->method, "POST") == 0 ? 0 : ENOSYS; /* XXX */
+ if (ret == 0 && local_hdb && local_hdb_read_only) {
+ /* Make sure we can write */
+ kadm5_destroy(r->kadm_handle);
+ r->kadm_handle = NULL;
+ ret = get_kadm_handle(r->context, r->realm, 1 /* want_write */,
+ &r->kadm_handle);
+ }
+ princ.attributes |= KRB5_KDB_MATERIALIZE;
+ princ.attributes &= ~KRB5_KDB_VIRTUAL;
+ /*
+ * XXX If there are TL data which should be re-encoded and sent as
+ * KRB5_TL_EXTENSION, then this call will fail with KADM5_BAD_TL_TYPE.
+ *
+ * We should either drop those TLs, re-encode them, or make
+ * perform_tl_data() handle them. (New extensions should generally go
+ * as KRB5_TL_EXTENSION so that non-critical ones can be set on
+ * principals via old kadmind programs that don't support them.)
+ *
+ * What we really want is a kadm5 utility function to convert some TLs
+ * to KRB5_TL_EXTENSION and drop all others.
+ */
+ if (ret == 0)
+ ret = kadm5_create_principal(r->kadm_handle, &princ, mask, "");
+ refetch = 1;
+ } /* else create/materialize q-params are superfluous */
+
+ /* Handle rotate / revoke options */
+ if (ret == 0 && change) {
+ krb5_keyblock *k = NULL;
+ size_t i;
+ int n_k = 0;
+ int keepold = r->revoke ? 0 : 1;
+
+ if (read_only)
+ ret = KADM5_READ_ONLY;
+ else
+ ret = strcmp(r->method, "POST") == 0 ? 0 : ENOSYS; /* XXX */
+ if (ret == 0 && local_hdb && local_hdb_read_only) {
+ /* Make sure we can write */
+ kadm5_destroy(r->kadm_handle);
+ r->kadm_handle = NULL;
+ ret = get_kadm_handle(r->context, r->realm, 1 /* want_write */,
+ &r->kadm_handle);
+ }
+
+ /* Use requested enctypes or same ones as princ already had keys for */
+ if (ret == 0 && kstuple == NULL)
+ ret = make_kstuple(r->context, &princ, &kstuple, &nkstuple);
+
+ /* Set new keys */
+ if (ret == 0)
+ ret = kadm5_randkey_principal_3(r->kadm_handle, p, keepold,
+ nkstuple, kstuple, &k, &n_k);
+ refetch = 1;
+ for (i = 0; n_k > 0 && i < n_k; i++)
+ krb5_free_keyblock_contents(r->context, &k[i]);
+ free(kstuple);
+ free(k);
+ }
+
+ if (ret == 0 && refetch) {
+ /* Refetch changed principal */
+ if (freeit)
+ kadm5_free_principal_ent(r->kadm_handle, &princ);
+ freeit = 0;
+ ret = kadm5_get_principal(r->kadm_handle, p, &princ, mask);
+ if (ret == 0)
+ freeit = 1;
+ }
+
+ if (ret == 0)
+ ret = write_keytab(r, &princ, pname);
+
+ if (ret == 0) {
+ /*
+ * We will use the principal's password expiration to work out the
+ * value for the max-age Cache-Control.
+ *
+ * Virtual service principals will have their `pw_expiration' set to a
+ * time when the client should refetch keys.
+ *
+ * Concrete service principals will generally not have a non-zero
+ * `pw_expiration', but if we have a new_service_key_delay, then we'll
+ * use half of it as the max-age Cache-Control.
+ */
+ if (princ.pw_expiration == 0) {
+ krb5_timestamp nskd =
+ krb5_config_get_time_default(r->context, NULL, 0, "hdb",
+ "new_service_key_delay", NULL);
+ if (nskd)
+ princ.pw_expiration = time(NULL) + (nskd >> 1);
+ }
+
+ /*
+ * This service can be used to fetch more than one principal's keys, so
+ * the max-age Cache-Control should be derived from the soonest-
+ * "expiring" principal.
+ */
+ if (r->pw_end == 0 ||
+ (princ.pw_expiration < r->pw_end && princ.pw_expiration > time(NULL)))
+ r->pw_end = princ.pw_expiration;
+ }
+ if (freeit)
+ kadm5_free_principal_ent(r->kadm_handle, &princ);
+ krb5_free_principal(r->context, p);
+ return ret;
+}
+
+static krb5_error_code check_csrf(kadmin_request_desc);
+
+/*
+ * Calls get_keys1() to extract each requested principal's keys.
+ *
+ * When this returns a response will have been set.
+ */
+static krb5_error_code
+get_keysN(kadmin_request_desc r)
+{
+ krb5_error_code ret;
+ size_t nhosts;
+ size_t nsvcs;
+ size_t nspns;
+ size_t i, k;
+
+ /* Parses and validates the request, then checks authorization */
+ ret = authorize_req(r);
+ if (ret)
+ return ret; /* authorize_req() calls bad_req() on error */
+
+ /*
+ * If we have a r->kadm_handle already it's because we validated a CSRF
+ * token. It may not be a handle to a realm we wanted though.
+ */
+ if (r->kadm_handle)
+ kadm5_destroy(r->kadm_handle);
+ r->kadm_handle = NULL;
+ ret = get_kadm_handle(r->context, r->realm ? r->realm : realm,
+ 0 /* want_write */, &r->kadm_handle);
+ if (ret)
+ return bad_404(r, ret, "Could not connect to realm");
+
+ nhosts = heim_array_get_length(r->hostnames);
+ nsvcs = heim_array_get_length(r->service_names);
+ nspns = heim_array_get_length(r->spns);
+ if (!nhosts && !nspns)
+ return bad_403(r, EINVAL, "No service principals requested");
+
+ if (nhosts && !nsvcs) {
+ heim_string_t s;
+
+ if ((s = heim_string_create("HTTP")) == NULL)
+ ret = krb5_enomem(r->context);
+ if (ret == 0)
+ ret = heim_array_append_value(r->service_names, s);
+ heim_release(s);
+ nsvcs = 1;
+ if (ret)
+ return bad_503(r, ret, "Out of memory");
+ }
+
+ if (nspns + nsvcs * nhosts >
+ krb5_config_get_int_default(r->context, NULL, 400,
+ "ext_keytab", "get_keys_max_spns", NULL))
+ return bad_403(r, EINVAL, "Requested keys for too many principals");
+
+ ret = make_keytab(r);
+ for (i = 0; ret == 0 && i < nsvcs; i++) {
+ const char *svc =
+ heim_string_get_utf8(
+ heim_array_get_value(r->service_names, i));
+
+ for (k = 0; ret == 0 && k < nhosts; k++) {
+ krb5_principal p = NULL;
+ const char *hostname =
+ heim_string_get_utf8(
+ heim_array_get_value(r->hostnames, k));
+ char *spn = NULL;
+
+ ret = krb5_make_principal(r->context, &p,
+ r->realm ? r->realm : realm,
+ svc, hostname, NULL);
+ if (ret == 0)
+ ret = krb5_unparse_name(r->context, p, &spn);
+ if (ret == 0)
+ ret = get_keys1(r, spn);
+ krb5_free_principal(r->context, p);
+ free(spn);
+ }
+ }
+ for (i = 0; ret == 0 && i < nspns; i++) {
+ ret = get_keys1(r,
+ heim_string_get_utf8(heim_array_get_value(r->spns,
+ i)));
+ }
+ switch (ret) {
+ case -1:
+ /* Can't happen */
+ krb5_log_msg(r->context, logfac, 1, NULL,
+ "Failed to extract keys for unknown reasons");
+ if (r->response_set)
+ return MHD_YES;
+ return bad_503(r, ret, "Could not get keys");
+ case ENOSYS:
+ /* Our convention */
+ return bad_method_want_POST(r);
+ case KADM5_READ_ONLY:
+ if (primary_server_URI) {
+ krb5_log_msg(r->context, logfac, 1, NULL,
+ "Redirect %s to primary server", r->cname);
+ return resp(r, MHD_HTTP_TEMPORARY_REDIRECT, KADM5_READ_ONLY,
+ MHD_RESPMEM_PERSISTENT, NULL, "", 0, NULL);
+ } else {
+ krb5_log_msg(r->context, logfac, 1, NULL, "HDB is read-only");
+ return bad_403(r, ret, "HDB is read-only");
+ }
+ case 0:
+ krb5_log_msg(r->context, logfac, 1, NULL, "Sent keytab to %s",
+ r->cname);
+ return good_ext_keytab(r);
+ default:
+ return bad_503(r, ret, "Could not get keys");
+ }
+}
+
+/* Copied from kdc/connect.c */
+static void
+addr_to_string(krb5_context context,
+ struct sockaddr *addr,
+ char *str,
+ size_t len)
+{
+ krb5_error_code ret;
+ krb5_address a;
+
+ ret = krb5_sockaddr2address(context, addr, &a);
+ if (ret == 0) {
+ ret = krb5_print_address(&a, str, len, &len);
+ krb5_free_address(context, &a);
+ }
+ if (ret)
+ snprintf(str, len, "<family=%d>", addr->sa_family);
+}
+
+static void clean_req_desc(kadmin_request_desc);
+
+static krb5_error_code
+set_req_desc(struct MHD_Connection *connection,
+ const char *method,
+ const char *url,
+ kadmin_request_desc *rp)
+{
+ const union MHD_ConnectionInfo *ci;
+ kadmin_request_desc r;
+ const char *token;
+ krb5_error_code ret;
+
+ *rp = NULL;
+ if ((r = calloc(1, sizeof(*r))) == NULL)
+ return ENOMEM;
+
+ (void) gettimeofday(&r->tv_start, NULL);
+ if ((ret = get_krb5_context(&r->context))) {
+ free(r);
+ return ret;
+ }
+ /* HEIM_SVC_REQUEST_DESC_COMMON_ELEMENTS fields */
+ r->request.data = "<HTTP-REQUEST>";
+ r->request.length = sizeof("<HTTP-REQUEST>");
+ r->from = r->frombuf;
+ r->free_list = NULL;
+ r->config = NULL;
+ r->logf = logfac;
+ r->reqtype = url;
+ r->reason = NULL;
+ r->reply = NULL;
+ r->sname = NULL;
+ r->cname = NULL;
+ r->addr = NULL;
+ r->kv = heim_dict_create(10);
+ r->pp = NULL;
+ r->attributes = heim_dict_create(1);
+ /* Our fields */
+ r->connection = connection;
+ r->kadm_handle = NULL;
+ r->hcontext = r->context->hcontext;
+ r->service_names = heim_array_create();
+ r->hostnames = heim_array_create();
+ r->spns = heim_array_create();
+ r->keytab_name = NULL;
+ r->enctypes = NULL;
+ r->cache_control = NULL;
+ r->freeme1 = NULL;
+ r->method = method;
+ r->cprinc = NULL;
+ r->req = NULL;
+ r->sp = NULL;
+ ci = MHD_get_connection_info(connection,
+ MHD_CONNECTION_INFO_CLIENT_ADDRESS);
+ if (ci) {
+ r->addr = ci->client_addr;
+ addr_to_string(r->context, r->addr, r->frombuf, sizeof(r->frombuf));
+ }
+
+ if (r->kv) {
+ heim_audit_addkv((heim_svc_req_desc)r, 0, "method", "GET");
+ heim_audit_addkv((heim_svc_req_desc)r, 0, "endpoint", "%s", r->reqtype);
+ }
+ token = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_AUTHORIZATION);
+ if (token && r->kv) {
+ const char *token_end;
+
+ if ((token_end = strchr(token, ' ')) == NULL ||
+ (token_end - token) > INT_MAX || (token_end - token) < 2)
+ heim_audit_addkv((heim_svc_req_desc)r, 0, "auth", "<unknown>");
+ else
+ heim_audit_addkv((heim_svc_req_desc)r, 0, "auth", "%.*s",
+ (int)(token_end - token), token);
+
+ }
+
+ if (ret == 0 && r->kv == NULL) {
+ krb5_log_msg(r->context, logfac, 1, NULL, "Out of memory");
+ ret = r->error_code = ENOMEM;
+ }
+ if (ret == 0)
+ *rp = r;
+ else
+ clean_req_desc(r);
+ return ret;
+}
+
+static void
+clean_req_desc(kadmin_request_desc r)
+{
+ if (!r)
+ return;
+
+ if (r->keytab)
+ krb5_kt_destroy(r->context, r->keytab);
+ else if (r->keytab_name && strchr(r->keytab_name, ':'))
+ (void) unlink(strchr(r->keytab_name, ':') + 1);
+ if (r->kadm_handle)
+ kadm5_destroy(r->kadm_handle);
+ if (r->pp)
+ MHD_destroy_post_processor(r->pp);
+ hx509_request_free(&r->req);
+ heim_release(r->service_names);
+ heim_release(r->attributes);
+ heim_release(r->hostnames);
+ heim_release(r->reason);
+ heim_release(r->spns);
+ heim_release(r->kv);
+ krb5_free_principal(r->context, r->cprinc);
+ free(r->cache_control);
+ free(r->keytab_name);
+ free(r->csrf_token);
+ free(r->enctypes);
+ free(r->freeme1);
+ free(r->cname);
+ free(r->sname);
+ free(r->realm);
+ free(r);
+}
+
+static void
+cleanup_req(void *cls,
+ struct MHD_Connection *connection,
+ void **con_cls,
+ enum MHD_RequestTerminationCode toe)
+{
+ kadmin_request_desc r = *con_cls;
+
+ (void)cls;
+ (void)connection;
+ (void)toe;
+ clean_req_desc(r);
+ *con_cls = NULL;
+}
+
+/* Implements GETs of /get-keys */
+static krb5_error_code
+get_keys(kadmin_request_desc r)
+{
+ if (r->cname == NULL || r->cprinc == NULL)
+ return bad_401(r, "Could not extract principal name from token");
+ return get_keysN(r); /* Sets an HTTP response */
+}
+
+/* Implements GETs of /get-config */
+static krb5_error_code
+get_config(kadmin_request_desc r)
+{
+
+ kadm5_principal_ent_rec princ;
+ krb5_error_code ret;
+ krb5_principal p = NULL;
+ uint32_t mask = KADM5_PRINCIPAL | KADM5_TL_DATA;
+ krb5_tl_data *tl_next;
+ const char *pname;
+ /* Default configuration for principals that have none set: */
+ size_t bodylen = sizeof("include /etc/krb5.conf\n") - 1;
+ void *body = "include /etc/krb5.conf\n";
+ int freeit = 0;
+
+ if (r->cname == NULL || r->cprinc == NULL)
+ return bad_401(r, "Could not extract principal name from token");
+ /*
+ * No authorization needed -- configs are public. Though we do require
+ * authentication (above).
+ */
+
+ ret = get_kadm_handle(r->context, r->realm ? r->realm : realm,
+ 0 /* want_write */, &r->kadm_handle);
+ if (ret)
+ return bad_503(r, ret, "Could not access KDC database");
+
+ memset(&princ, 0, sizeof(princ));
+ princ.key_data = NULL;
+ princ.tl_data = NULL;
+
+ pname = MHD_lookup_connection_value(r->connection, MHD_GET_ARGUMENT_KIND,
+ "princ");
+ if (pname == NULL)
+ pname = r->cname;
+ ret = krb5_parse_name(r->context, pname, &p);
+ if (ret == 0) {
+ ret = kadm5_get_principal(r->kadm_handle, p, &princ, mask);
+ if (ret == 0) {
+ freeit = 1;
+ for (tl_next = princ.tl_data; tl_next; tl_next = tl_next->tl_data_next) {
+ if (tl_next->tl_data_type != KRB5_TL_KRB5_CONFIG)
+ continue;
+ bodylen = tl_next->tl_data_length;
+ body = tl_next->tl_data_contents;
+ break;
+ }
+ } else {
+ r->error_code = ret;
+ return bad_404(r, ret, "/get-config");
+ }
+ }
+
+ if (ret == 0) {
+ krb5_log_msg(r->context, logfac, 1, NULL,
+ "Returned krb5.conf contents to %s", r->cname);
+ ret = resp(r, MHD_HTTP_OK, 0, MHD_RESPMEM_MUST_COPY,
+ "application/text", body, bodylen, NULL);
+ } else {
+ ret = bad_503(r, ret, "Could not retrieve principal configuration");
+ }
+ if (freeit)
+ kadm5_free_principal_ent(r->kadm_handle, &princ);
+ krb5_free_principal(r->context, p);
+ return ret;
+}
+
+static krb5_error_code
+mac_csrf_token(kadmin_request_desc r, krb5_storage *sp)
+{
+ kadm5_principal_ent_rec princ;
+ krb5_error_code ret;
+ krb5_principal p = NULL;
+ krb5_data data;
+ char mac[EVP_MAX_MD_SIZE];
+ unsigned int maclen = sizeof(mac);
+ HMAC_CTX *ctx = NULL;
+ size_t i = 0;
+ int freeit = 0;
+
+ memset(&princ, 0, sizeof(princ));
+ ret = krb5_storage_to_data(sp, &data);
+ if (r->kadm_handle == NULL)
+ ret = get_kadm_handle(r->context,
+ r->realm ? r->realm : realm,
+ 0 /* want_write */,
+ &r->kadm_handle);
+ if (ret == 0)
+ ret = krb5_make_principal(r->context, &p,
+ r->realm ? r->realm : realm,
+ "WELLKNOWN", "CSRFTOKEN", NULL);
+ if (ret == 0)
+ ret = kadm5_get_principal(r->kadm_handle, p, &princ,
+ KADM5_PRINCIPAL | KADM5_KVNO |
+ KADM5_KEY_DATA);
+ if (ret == 0)
+ freeit = 1;
+ if (ret == 0 && princ.n_key_data < 1)
+ ret = KADM5_UNK_PRINC;
+ if (ret == 0)
+ for (i = 0; i < princ.n_key_data; i++)
+ if (princ.key_data[i].key_data_kvno == princ.kvno)
+ break;
+ if (ret == 0 && i == princ.n_key_data)
+ i = 0; /* Weird, but can't happen */
+
+ if (ret == 0 && (ctx = HMAC_CTX_new()) == NULL)
+ ret = krb5_enomem(r->context);
+ /* HMAC the token body and the client principal name */
+ if (ret == 0) {
+ if (HMAC_Init_ex(ctx, princ.key_data[i].key_data_contents[0],
+ princ.key_data[i].key_data_length[0], EVP_sha256(),
+ NULL) == 0) {
+ HMAC_CTX_cleanup(ctx);
+ ret = krb5_enomem(r->context);
+ } else {
+ HMAC_Update(ctx, data.data, data.length);
+ HMAC_Update(ctx, r->cname, strlen(r->cname));
+ HMAC_Final(ctx, mac, &maclen);
+ HMAC_CTX_cleanup(ctx);
+ krb5_data_free(&data);
+ data.length = maclen;
+ data.data = mac;
+ if (krb5_storage_write(sp, mac, maclen) != maclen)
+ ret = krb5_enomem(r->context);
+ }
+ }
+ krb5_free_principal(r->context, p);
+ if (freeit)
+ kadm5_free_principal_ent(r->kadm_handle, &princ);
+ if (ctx)
+ HMAC_CTX_free(ctx);
+ return ret;
+}
+
+static krb5_error_code
+make_csrf_token(kadmin_request_desc r,
+ const char *given,
+ char **token,
+ int64_t *age)
+{
+ krb5_error_code ret = 0;
+ unsigned char given_decoded[128];
+ krb5_storage *sp = NULL;
+ krb5_data data;
+ ssize_t dlen = -1;
+ uint64_t nonce;
+ int64_t t = 0;
+
+
+ *age = 0;
+ data.data = NULL;
+ data.length = 0;
+ if (given) {
+ size_t len = strlen(given);
+
+ if (len >= sizeof(given_decoded))
+ ret = ERANGE;
+ if (ret == 0 && (dlen = rk_base64_decode(given, &given_decoded)) <= 0)
+ ret = errno;
+ if (ret == 0 &&
+ (sp = krb5_storage_from_mem(given_decoded, dlen)) == NULL)
+ ret = krb5_enomem(r->context);
+ if (ret == 0)
+ ret = krb5_ret_int64(sp, &t);
+ if (ret == 0)
+ ret = krb5_ret_uint64(sp, &nonce);
+ krb5_storage_free(sp);
+ sp = NULL;
+ if (ret == 0)
+ *age = time(NULL) - t;
+ } else {
+ t = time(NULL);
+ krb5_generate_random_block((void *)&nonce, sizeof(nonce));
+ }
+
+ if (ret == 0 && (sp = krb5_storage_emem()) == NULL)
+ ret = krb5_enomem(r->context);
+ if (ret == 0)
+ ret = krb5_store_int64(sp, t);
+ if (ret == 0)
+ ret = krb5_store_uint64(sp, nonce);
+ if (ret == 0)
+ ret = mac_csrf_token(r, sp);
+ if (ret == 0)
+ ret = krb5_storage_to_data(sp, &data);
+ if (ret == 0 && data.length > INT_MAX)
+ ret = ERANGE;
+ if (ret == 0 &&
+ rk_base64_encode(data.data, data.length, token) < 0)
+ ret = errno;
+ krb5_storage_free(sp);
+ krb5_data_free(&data);
+ return ret;
+}
+
+/*
+ * Returns system or krb5_error_code on error, but also calls resp() or bad_*()
+ * on error.
+ */
+static krb5_error_code
+check_csrf(kadmin_request_desc r)
+{
+ krb5_error_code ret;
+ const char *given;
+ int64_t age;
+ size_t givenlen, expectedlen;
+
+ if ((((csrf_prot_type & CSRF_PROT_GET_WITH_HEADER) &&
+ strcmp(r->method, "GET") == 0) ||
+ ((csrf_prot_type & CSRF_PROT_POST_WITH_HEADER) &&
+ strcmp(r->method, "POST") == 0)) &&
+ MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
+ csrf_header) == NULL) {
+ ret = bad_req(r, EACCES, MHD_HTTP_FORBIDDEN,
+ "Request must have header \"%s\"", csrf_header);
+ return ret == -1 ? MHD_NO : MHD_YES;
+ }
+
+ if (strcmp(r->method, "GET") == 0 &&
+ !(csrf_prot_type & CSRF_PROT_GET_WITH_TOKEN))
+ return 0;
+ if (strcmp(r->method, "POST") == 0 &&
+ !(csrf_prot_type & CSRF_PROT_POST_WITH_TOKEN))
+ return 0;
+
+ given = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
+ "X-CSRF-Token");
+ ret = make_csrf_token(r, given, &r->csrf_token, &age);
+ if (ret)
+ return bad_503(r, ret, "Could not create a CSRF token");
+ /*
+ * If CSRF token needed but missing, call resp() directly, bypassing
+ * bad_403(), to return a 403 with an expected CSRF token in the response.
+ */
+ if (given == NULL) {
+ (void) resp(r, MHD_HTTP_FORBIDDEN, ENOSYS, MHD_RESPMEM_PERSISTENT,
+ NULL, "CSRF token needed; copy the X-CSRF-Token: response "
+ "header to your next POST", BODYLEN_IS_STRLEN, NULL);
+ return ENOSYS;
+ }
+
+ /* Validate the CSRF token for this request */
+ givenlen = strlen(given);
+ expectedlen = strlen(r->csrf_token);
+ if (givenlen != expectedlen || ct_memcmp(given, r->csrf_token, givenlen)) {
+ (void) bad_403(r, EACCES, "Invalid CSRF token");
+ return EACCES;
+ }
+ if (age > 300) { /* XXX */
+ (void) bad_403(r, EACCES, "CSRF token too old");
+ return EACCES;
+ }
+ return 0;
+}
+
+static krb5_error_code
+health(const char *method, kadmin_request_desc r)
+{
+ if (strcmp(method, "HEAD") == 0) {
+ return resp(r, MHD_HTTP_OK, 0, MHD_RESPMEM_PERSISTENT, NULL, "", 0,
+ NULL);
+ }
+ return resp(r, MHD_HTTP_OK, 0, MHD_RESPMEM_PERSISTENT, NULL,
+ "To determine the health of the service, use the /get-config "
+ "end-point.\n", BODYLEN_IS_STRLEN, NULL);
+
+}
+
+static heim_mhd_result
+ip(void *cls,
+ enum MHD_ValueKind kind,
+ const char *key,
+ const char *content_name,
+ const char *content_type,
+ const char *transfer_encoding,
+ const char *val,
+ uint64_t off,
+ size_t size)
+{
+ kadmin_request_desc r = cls;
+ struct free_tend_list *ftl = calloc(1, sizeof(*ftl));
+ char *keydup = strdup(key);
+ char *valdup = strndup(val, size);
+
+ (void)content_name; /* MIME attachment name */
+ (void)content_type;
+ (void)transfer_encoding;
+ (void)off; /* Offset in POST data */
+
+ /* We're going to MHD_set_connection_value(), but we need copies */
+ if (ftl == NULL || keydup == NULL || valdup == NULL) {
+ free(ftl);
+ free(keydup);
+ free(valdup);
+ return MHD_NO;
+ }
+ ftl->freeme1 = keydup;
+ ftl->freeme2 = valdup;
+ ftl->next = r->free_list;
+ r->free_list = ftl;
+
+ return MHD_set_connection_value(r->connection, MHD_GET_ARGUMENT_KIND,
+ keydup, valdup);
+}
+
+typedef krb5_error_code (*handler)(struct kadmin_request_desc *);
+
+struct route {
+ const char *local_part;
+ handler h;
+} routes[] = {
+ { "/get-keys", get_keys },
+ { "/get-config", get_config },
+};
+
+static heim_mhd_result
+route(void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **ctx)
+{
+ struct kadmin_request_desc *r = *ctx;
+ size_t i;
+ int ret;
+
+ if (r == NULL) {
+ /*
+ * This is the first call, right after headers were read.
+ *
+ * We must return quickly so that any 100-Continue might be sent with
+ * celerity. We want to make sure to send any 401s early, so we check
+ * WWW-Authenticate now, not later.
+ *
+ * We'll get called again to really do the processing. If we're
+ * handling a POST then we'll also get called with upload_data != NULL,
+ * possibly multiple times.
+ */
+ if ((ret = set_req_desc(connection, method, url, &r)))
+ return MHD_NO;
+ *ctx = r;
+
+ /*
+ * All requests other than /health require authentication and CSRF
+ * protection.
+ */
+ if (strcmp(url, "/health") == 0)
+ return MHD_YES;
+
+ /* Authenticate and do CSRF protection */
+ ret = validate_token(r);
+ if (ret == 0)
+ ret = check_csrf(r);
+
+ /*
+ * As this is the initial call to this handler, we must return now.
+ *
+ * If authentication or CSRF protection failed then we'll already have
+ * enqueued a 401, 403, or 5xx response and then we're done.
+ *
+ * If both authentication and CSRF protection succeeded then no
+ * response has been queued up and we'll get called again to finally
+ * process the request, then this entire if block will not be executed.
+ */
+ return ret == -1 ? MHD_NO : MHD_YES;
+ }
+
+ /* Validate HTTP method */
+ if (strcmp(method, "GET") != 0 &&
+ strcmp(method, "POST") != 0 &&
+ strcmp(method, "HEAD") != 0) {
+ return bad_405(r, method) == -1 ? MHD_NO : MHD_YES;
+ }
+
+ if ((strcmp(method, "HEAD") == 0 || strcmp(method, "GET") == 0) &&
+ (strcmp(url, "/health") == 0 || strcmp(url, "/") == 0)) {
+ /* /health end-point -- no authentication, no CSRF, no nothing */
+ return health(method, r) == -1 ? MHD_NO : MHD_YES;
+ }
+
+ if (strcmp(method, "POST") == 0 && *upload_data_size != 0) {
+ /*
+ * Consume all the POST body and set form data as MHD_GET_ARGUMENT_KIND
+ * (as if they had been URI query parameters).
+ *
+ * We have to do this before we can MHD_queue_response() as MHD will
+ * not consume the rest of the request body on its own, so it's an
+ * error to MHD_queue_response() before we've done this, and if we do
+ * then MHD just closes the connection.
+ *
+ * 4KB should be more than enough buffer space for all the keys we
+ * expect.
+ */
+ if (r->pp == NULL)
+ r->pp = MHD_create_post_processor(connection, 4096, ip, r);
+ if (r->pp == NULL) {
+ ret = bad_503(r, errno ? errno : ENOMEM,
+ "Could not consume POST data");
+ return ret == -1 ? MHD_NO : MHD_YES;
+ }
+ if (r->post_data_size + *upload_data_size > 1UL<<17) {
+ return bad_413(r) == -1 ? MHD_NO : MHD_YES;
+ }
+ r->post_data_size += *upload_data_size;
+ if (MHD_post_process(r->pp, upload_data,
+ *upload_data_size) == MHD_NO) {
+ ret = bad_503(r, errno ? errno : ENOMEM,
+ "Could not consume POST data");
+ return ret == -1 ? MHD_NO : MHD_YES;
+ }
+ *upload_data_size = 0;
+ return MHD_YES;
+ }
+
+ /*
+ * Either this is a HEAD, a GET, or a POST whose request body has now been
+ * received completely and processed.
+ */
+
+ /* Allow GET? */
+ if (strcmp(method, "GET") == 0 && !allow_GET_flag) {
+ /* No */
+ return bad_405(r, method) == -1 ? MHD_NO : MHD_YES;
+ }
+
+ for (i = 0; i < sizeof(routes)/sizeof(routes[0]); i++) {
+ if (strcmp(url, routes[i].local_part) != 0)
+ continue;
+ if (MHD_lookup_connection_value(r->connection,
+ MHD_HEADER_KIND,
+ "Referer") != NULL) {
+ ret = bad_req(r, EACCES, MHD_HTTP_FORBIDDEN,
+ "GET from browser not allowed");
+ return ret == -1 ? MHD_NO : MHD_YES;
+ }
+ if (strcmp(method, "HEAD") == 0)
+ ret = resp(r, MHD_HTTP_OK, 0, MHD_RESPMEM_PERSISTENT, NULL, "", 0,
+ NULL);
+ else
+ ret = routes[i].h(r);
+ return ret == -1 ? MHD_NO : MHD_YES;
+ }
+
+ ret = bad_404(r, ENOENT, url);
+ return ret == -1 ? MHD_NO : MHD_YES;
+}
+
+static struct getargs args[] = {
+ { "help", 'h', arg_flag, &help_flag, "Print usage message", NULL },
+ { "version", '\0', arg_flag, &version_flag, "Print version", NULL },
+ { NULL, 'H', arg_strings, &audiences,
+ "expected token audience(s) of the service", "HOSTNAME" },
+ { "allow-GET", 0, arg_negative_flag,
+ &allow_GET_flag, NULL, NULL },
+ { "csrf-header", 0, arg_string, &csrf_header,
+ "required request header", "HEADER-NAME" },
+ { "daemon", 'd', arg_flag, &daemonize, "daemonize", "daemonize" },
+ { "daemon-child", 0, arg_flag, &daemon_child_fd, NULL, NULL }, /* priv */
+ { "reverse-proxied", 0, arg_flag, &reverse_proxied_flag,
+ "reverse proxied", "listen on 127.0.0.1 and do not use TLS" },
+ { NULL, 'p', arg_integer, &port, "PORT", "port number (default: 443)" },
+ { "temp-dir", 0, arg_string, &cache_dir,
+ "cache directory", "DIRECTORY" },
+ { "cert", 0, arg_string, &cert_file,
+ "certificate file path (PEM)", "HX509-STORE" },
+ { "private-key", 0, arg_string, &priv_key_file,
+ "private key file path (PEM)", "HX509-STORE" },
+ { "thread-per-client", 't', arg_flag, &thread_per_client_flag, "thread per-client", NULL },
+ { "realm", 0, arg_string, &realm, "realm", "REALM" },
+ { "hdb", 0, arg_string, &hdb, "HDB filename", "PATH" },
+ { "read-only-admin-server", 0, arg_string, &kadmin_server,
+ "Name of read-only kadmin server", "HOST[:PORT]" },
+ { "writable-admin-server", 0, arg_string, &writable_kadmin_server,
+ "Name of writable kadmin server", "HOST[:PORT]" },
+ { "primary-server-uri", 0, arg_string, &primary_server_URI,
+ "Name of primary httpkadmind server for HTTP redirects", "URL" },
+ { "local", 'l', arg_flag, &local_hdb,
+ "Use a local HDB as read-only", NULL },
+ { "local-read-only", 0, arg_flag, &local_hdb_read_only,
+ "Use a local HDB as read-only", NULL },
+ { "read-only", 0, arg_flag, &read_only, "Allow no writes", NULL },
+ { "stash-file", 0, arg_string, &stash_file,
+ "Stash file for HDB", "PATH" },
+ { "kadmin-client-name", 0, arg_string, &kadmin_client_name,
+ "Client name for remote kadmind", "PRINCIPAL" },
+ { "kadmin-client-keytab", 0, arg_string, &kadmin_client_keytab,
+ "Keytab with client credentials for remote kadmind", "KEYTAB" },
+ { "token-authentication-type", 'T', arg_strings, &auth_types,
+ "Token authentication type(s) supported", "HTTP-AUTH-TYPE" },
+ { "verbose", 'v', arg_counter, &verbose_counter, "verbose", "run verbosely" }
+};
+
+static int
+usage(int e)
+{
+ arg_printusage(args, sizeof(args) / sizeof(args[0]), "httpkadmind",
+ "\nServes an HTTP API for getting (and rotating) service "
+ "principal keys, and other kadmin-like operations\n");
+ exit(e);
+}
+
+static int sigpipe[2] = { -1, -1 };
+
+static void
+sighandler(int sig)
+{
+ char c = sig;
+ while (write(sigpipe[1], &c, sizeof(c)) == -1 && errno == EINTR)
+ ;
+}
+
+static void
+my_openlog(krb5_context context,
+ const char *svc,
+ krb5_log_facility **fac)
+{
+ char **s = NULL, **p;
+
+ krb5_initlog(context, "httpkadmind", fac);
+ s = krb5_config_get_strings(context, NULL, svc, "logging", NULL);
+ if (s == NULL)
+ s = krb5_config_get_strings(context, NULL, "logging", svc, NULL);
+ if (s) {
+ for(p = s; *p; p++)
+ krb5_addlog_dest(context, *fac, *p);
+ krb5_config_free_strings(s);
+ } else {
+ char *ss;
+ if (asprintf(&ss, "0-1/FILE:%s/%s", hdb_db_dir(context),
+ KDC_LOG_FILE) < 0)
+ err(1, "out of memory");
+ krb5_addlog_dest(context, *fac, ss);
+ free(ss);
+ }
+ krb5_set_warn_dest(context, *fac);
+}
+
+static const char *sysplugin_dirs[] = {
+#ifdef _WIN32
+ "$ORIGIN",
+#else
+ "$ORIGIN/../lib/plugin/kdc",
+#endif
+#ifdef __APPLE__
+ LIBDIR "/plugin/kdc",
+#endif
+ NULL
+};
+
+static void
+load_plugins(krb5_context context)
+{
+ const char * const *dirs = sysplugin_dirs;
+#ifndef _WIN32
+ char **cfdirs;
+
+ cfdirs = krb5_config_get_strings(context, NULL, "kdc", "plugin_dir", NULL);
+ if (cfdirs)
+ dirs = (const char * const *)cfdirs;
+#endif
+
+ /* XXX kdc? */
+ _krb5_load_plugins(context, "kdc", (const char **)dirs);
+
+#ifndef _WIN32
+ krb5_config_free_strings(cfdirs);
+#endif
+}
+
+static void
+get_csrf_prot_type(krb5_context context)
+{
+ char * const *strs = csrf_prot_type_strs.strings;
+ size_t n = csrf_prot_type_strs.num_strings;
+ size_t i;
+ char **freeme = NULL;
+
+ if (csrf_header == NULL)
+ csrf_header = krb5_config_get_string(context, NULL, "bx509d",
+ "csrf_protection_csrf_header",
+ NULL);
+
+ if (n == 0) {
+ char * const *p;
+
+ strs = freeme = krb5_config_get_strings(context, NULL, "bx509d",
+ "csrf_protection_type", NULL);
+ for (p = strs; p && p; p++)
+ n++;
+ }
+
+ for (i = 0; i < n; i++) {
+ if (strcmp(strs[i], "GET-with-header") == 0)
+ csrf_prot_type |= CSRF_PROT_GET_WITH_HEADER;
+ else if (strcmp(strs[i], "GET-with-token") == 0)
+ csrf_prot_type |= CSRF_PROT_GET_WITH_TOKEN;
+ else if (strcmp(strs[i], "POST-with-header") == 0)
+ csrf_prot_type |= CSRF_PROT_POST_WITH_HEADER;
+ else if (strcmp(strs[i], "POST-with-token") == 0)
+ csrf_prot_type |= CSRF_PROT_POST_WITH_TOKEN;
+ }
+ free(freeme);
+
+ /*
+ * For GETs we default to no CSRF protection as our GETable resources are
+ * safe and idempotent and we count on the browser not to make the
+ * responses available to cross-site requests.
+ *
+ * But, really, we don't want browsers even making these requests since, if
+ * the browsers behave correctly, then there's no point, and if they don't
+ * behave correctly then that could be catastrophic. Of course, there's no
+ * guarantee that a browser won't have other catastrophic bugs, but still,
+ * we should probably change this default in the future:
+ *
+ * if (!(csrf_prot_type & CSRF_PROT_GET_WITH_HEADER) &&
+ * !(csrf_prot_type & CSRF_PROT_GET_WITH_TOKEN))
+ * csrf_prot_type |= <whatever-the-new-default-should-be>;
+ */
+
+ /*
+ * For POSTs we default to CSRF protection with anti-CSRF tokens even
+ * though out POSTable resources are safe and idempotent when POSTed and we
+ * could count on the browser not to make the responses available to
+ * cross-site requests.
+ */
+ if (!(csrf_prot_type & CSRF_PROT_POST_WITH_HEADER) &&
+ !(csrf_prot_type & CSRF_PROT_POST_WITH_TOKEN))
+ csrf_prot_type |= CSRF_PROT_POST_WITH_TOKEN;
+}
+
+int
+main(int argc, char **argv)
+{
+ unsigned int flags = MHD_USE_THREAD_PER_CONNECTION; /* XXX */
+ struct sockaddr_in sin;
+ struct MHD_Daemon *previous = NULL;
+ struct MHD_Daemon *current = NULL;
+ struct sigaction sa;
+ krb5_context context = NULL;
+ MHD_socket sock = MHD_INVALID_SOCKET;
+ void *kadm_handle;
+ char *priv_key_pem = NULL;
+ char *cert_pem = NULL;
+ char sig;
+ int optidx = 0;
+ int ret;
+
+ setprogname("httpkadmind");
+ if (getarg(args, sizeof(args) / sizeof(args[0]), argc, argv, &optidx))
+ usage(1);
+ if (help_flag)
+ usage(0);
+ if (version_flag) {
+ print_version(NULL);
+ exit(0);
+ }
+ if (argc > optidx) /* Add option to set a URI local part prefix? */
+ usage(1);
+ if (port < 0)
+ errx(1, "Port number must be given");
+
+ if (writable_kadmin_server == NULL && kadmin_server == NULL &&
+ !local_hdb && !local_hdb_read_only)
+ errx(1, "One of --local or --local-read-only must be given, or a "
+ "remote kadmind must be given");
+
+ if (audiences.num_strings == 0) {
+ char localhost[MAXHOSTNAMELEN];
+
+ ret = gethostname(localhost, sizeof(localhost));
+ if (ret == -1)
+ errx(1, "Could not determine local hostname; use --audience");
+
+ if ((audiences.strings =
+ calloc(1, sizeof(audiences.strings[0]))) == NULL ||
+ (audiences.strings[0] = strdup(localhost)) == NULL)
+ err(1, "Out of memory");
+ audiences.num_strings = 1;
+ }
+
+ if (daemonize && daemon_child_fd == -1)
+ daemon_child_fd = roken_detach_prep(argc, argv, "--daemon-child");
+ daemonize = 0;
+
+ argc -= optidx;
+ argv += optidx;
+ if (argc != 0)
+ usage(1);
+
+ if ((errno = pthread_key_create(&k5ctx, k5_free_context)))
+ err(1, "Could not create thread-specific storage");
+
+ if ((errno = get_krb5_context(&context)))
+ err(1, "Could not init krb5 context (config file issue?)");
+
+ get_csrf_prot_type(context);
+
+ if (!realm) {
+ char *s;
+
+ ret = krb5_get_default_realm(context, &s);
+ if (ret)
+ krb5_err(context, 1, ret, "Could not determine default realm");
+ realm = s;
+ }
+
+ if ((errno = get_kadm_handle(context, realm, 0 /* want_write */,
+ &kadm_handle)))
+ err(1, "Could not connect to HDB");
+ kadm5_destroy(kadm_handle);
+ kadm_handle = NULL;
+
+ my_openlog(context, "httpkadmind", &logfac);
+ load_plugins(context);
+
+ if (cache_dir == NULL) {
+ char *s = NULL;
+
+ if (asprintf(&s, "%s/httpkadmind-XXXXXX",
+ getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp") == -1 ||
+ s == NULL ||
+ (cache_dir = mkdtemp(s)) == NULL)
+ err(1, "could not create temporary cache directory");
+ if (verbose_counter)
+ fprintf(stderr, "Note: using %s as cache directory\n", cache_dir);
+ atexit(rm_cache_dir);
+ setenv("TMPDIR", cache_dir, 1);
+ }
+
+again:
+ if (cert_file && !priv_key_file)
+ priv_key_file = cert_file;
+
+ if (cert_file) {
+ hx509_cursor cursor = NULL;
+ hx509_certs certs = NULL;
+ hx509_cert cert = NULL;
+ time_t min_cert_life = 0;
+ size_t len;
+ void *s;
+
+ ret = hx509_certs_init(context->hx509ctx, cert_file, 0, NULL, &certs);
+ if (ret == 0)
+ ret = hx509_certs_start_seq(context->hx509ctx, certs, &cursor);
+ while (ret == 0 &&
+ (ret = hx509_certs_next_cert(context->hx509ctx, certs,
+ cursor, &cert)) == 0 && cert) {
+ time_t notAfter = 0;
+
+ if (!hx509_cert_have_private_key_only(cert) &&
+ (notAfter = hx509_cert_get_notAfter(cert)) <= time(NULL) + 30)
+ errx(1, "One or more certificates in %s are expired",
+ cert_file);
+ if (notAfter) {
+ notAfter -= time(NULL);
+ if (notAfter < 600)
+ warnx("One or more certificates in %s expire soon",
+ cert_file);
+ /* Reload 5 minutes prior to expiration */
+ if (notAfter < min_cert_life || min_cert_life < 1)
+ min_cert_life = notAfter;
+ }
+ hx509_cert_free(cert);
+ }
+ if (certs)
+ (void) hx509_certs_end_seq(context->hx509ctx, certs, cursor);
+ if (min_cert_life > 4)
+ alarm(min_cert_life >> 1);
+ hx509_certs_free(&certs);
+ if (ret)
+ hx509_err(context->hx509ctx, 1, ret,
+ "could not read certificate from %s", cert_file);
+
+ if ((errno = rk_undumpdata(cert_file, &s, &len)) ||
+ (cert_pem = strndup(s, len)) == NULL)
+ err(1, "could not read certificate from %s", cert_file);
+ if (strlen(cert_pem) != len)
+ err(1, "NULs in certificate file contents: %s", cert_file);
+ free(s);
+ }
+
+ if (priv_key_file) {
+ size_t len;
+ void *s;
+
+ if ((errno = rk_undumpdata(priv_key_file, &s, &len)) ||
+ (priv_key_pem = strndup(s, len)) == NULL)
+ err(1, "could not read private key from %s", priv_key_file);
+ if (strlen(priv_key_pem) != len)
+ err(1, "NULs in private key file contents: %s", priv_key_file);
+ free(s);
+ }
+
+ if (verbose_counter > 1)
+ flags |= MHD_USE_DEBUG;
+ if (thread_per_client_flag)
+ flags |= MHD_USE_THREAD_PER_CONNECTION;
+
+
+ if (pipe(sigpipe) == -1)
+ err(1, "Could not set up key/cert reloading");
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = sighandler;
+ if (reverse_proxied_flag) {
+ /*
+ * We won't use TLS in the reverse proxy case, so no need to reload
+ * certs. But we'll still read them if given, and alarm() will get
+ * called.
+ *
+ * XXX We should be able to re-read krb5.conf and such on SIGHUP.
+ */
+ (void) signal(SIGHUP, SIG_IGN);
+ (void) signal(SIGUSR1, SIG_IGN);
+ (void) signal(SIGALRM, SIG_IGN);
+ } else {
+ (void) sigaction(SIGHUP, &sa, NULL); /* Reload key & cert */
+ (void) sigaction(SIGUSR1, &sa, NULL); /* Reload key & cert */
+ (void) sigaction(SIGALRM, &sa, NULL); /* Reload key & cert */
+ }
+ (void) sigaction(SIGINT, &sa, NULL); /* Graceful shutdown */
+ (void) sigaction(SIGTERM, &sa, NULL); /* Graceful shutdown */
+ (void) signal(SIGPIPE, SIG_IGN);
+
+ if (previous)
+ sock = MHD_quiesce_daemon(previous);
+
+ if (reverse_proxied_flag) {
+ /*
+ * XXX IPv6 too. Create the sockets and tell MHD_start_daemon() about
+ * them.
+ */
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(port);
+ current = MHD_start_daemon(flags, port,
+ /*
+ * This is a connection access callback. We
+ * don't use it.
+ */
+ NULL, NULL,
+ /* This is our request handler */
+ route, (char *)NULL,
+ MHD_OPTION_SOCK_ADDR, &sin,
+ MHD_OPTION_CONNECTION_LIMIT, (unsigned int)200,
+ MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)10,
+ /* This is our request cleanup handler */
+ MHD_OPTION_NOTIFY_COMPLETED, cleanup_req, NULL,
+ MHD_OPTION_END);
+ } else if (sock != MHD_INVALID_SOCKET) {
+ /*
+ * Certificate/key rollover: reuse the listen socket returned by
+ * MHD_quiesce_daemon().
+ */
+ current = MHD_start_daemon(flags | MHD_USE_SSL, port,
+ NULL, NULL,
+ route, (char *)NULL,
+ MHD_OPTION_HTTPS_MEM_KEY, priv_key_pem,
+ MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
+ MHD_OPTION_CONNECTION_LIMIT, (unsigned int)200,
+ MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)10,
+ MHD_OPTION_LISTEN_SOCKET, sock,
+ MHD_OPTION_NOTIFY_COMPLETED, cleanup_req, NULL,
+ MHD_OPTION_END);
+ sock = MHD_INVALID_SOCKET;
+ } else {
+ current = MHD_start_daemon(flags | MHD_USE_SSL, port,
+ NULL, NULL,
+ route, (char *)NULL,
+ MHD_OPTION_HTTPS_MEM_KEY, priv_key_pem,
+ MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
+ MHD_OPTION_CONNECTION_LIMIT, (unsigned int)200,
+ MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)10,
+ MHD_OPTION_NOTIFY_COMPLETED, cleanup_req, NULL,
+ MHD_OPTION_END);
+ }
+ if (current == NULL)
+ err(1, "Could not start kadmin REST service");
+
+ if (previous) {
+ MHD_stop_daemon(previous);
+ previous = NULL;
+ }
+
+ if (verbose_counter)
+ fprintf(stderr, "Ready!\n");
+ if (daemon_child_fd != -1)
+ roken_detach_finish(NULL, daemon_child_fd);
+
+ /* Wait for signal, possibly SIGALRM, to reload certs and/or exit */
+ while ((ret = read(sigpipe[0], &sig, sizeof(sig))) == -1 &&
+ errno == EINTR)
+ ;
+
+ free(priv_key_pem);
+ free(cert_pem);
+ priv_key_pem = NULL;
+ cert_pem = NULL;
+
+ if (ret == 1 && (sig == SIGHUP || sig == SIGUSR1 || sig == SIGALRM)) {
+ /* Reload certs and restart service gracefully */
+ previous = current;
+ current = NULL;
+ goto again;
+ }
+
+ MHD_stop_daemon(current);
+ _krb5_unload_plugins(context, "kdc");
+ pthread_key_delete(k5ctx);
+ return 0;
+}
diff --git a/third_party/heimdal/kdc/ipc_csr_authorizer.c b/third_party/heimdal/kdc/ipc_csr_authorizer.c
new file mode 100644
index 0000000..f2ce03c
--- /dev/null
+++ b/third_party/heimdal/kdc/ipc_csr_authorizer.c
@@ -0,0 +1,681 @@
+/*
+ * Copyright (c) 2019 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This plugin authorizes requested certificate SANs and EKUs by calling a
+ * service over IPC (Unix domain sockets on Linux/BSD/Illumos).
+ *
+ * The IPC protocol is request/response, with requests and responses sent as
+ *
+ * <length><string>
+ *
+ * where the <length> is 4 bytes, unsigned binary in network byte order, and
+ * <string> is an array of <length> bytes and does NOT include a NUL
+ * terminator.
+ *
+ * Requests are of the form:
+ *
+ * check <princ> <exttype>=<extvalue> ...
+ *
+ * where <princ> is a URL-escaped principal name, <exttype> is one of:
+ *
+ * - san_pkinit
+ * - san_xmpp
+ * - san_email
+ * - san_ms_upn
+ * - san_dnsname
+ * - eku
+ *
+ * and <extvalue> is a URL-escaped string representation of the SAN or OID.
+ *
+ * OIDs are in the form 1.2.3.4.5.6.
+ *
+ * Only characters other than alphanumeric, '@', '.', '-', '_', and '/' are
+ * URL-encoded.
+ *
+ * Responses are any of:
+ *
+ * - granted
+ * - denied
+ * - error message
+ *
+ * Example:
+ *
+ * C->S: check jane@TEST.H5L.SE san_dnsname=jane.foo.test.h5l.se eku=1.3.6.1.5.5.7.3.1
+ * S->C: granted
+ *
+ * Only digitalSignature and nonRepudiation key usages are allowed. Requested
+ * key usages are not sent to the CSR authorizer IPC server.
+ */
+
+#define _GNU_SOURCE 1
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <roken.h>
+#include <heim-ipc.h>
+#include <krb5.h>
+#include <hx509.h>
+#include <kdc.h>
+#include <common_plugin.h>
+#include <csr_authorizer_plugin.h>
+
+/*
+ * string_encode_sz() and string_encode() encode principal names and such to be
+ * safe for use in our IPC text messages. They function very much like URL
+ * encoders, but '~' also gets encoded, and '.' and '@' do not.
+ *
+ * An unescaper is not needed here.
+ */
+static size_t
+string_encode_sz(const char *in)
+{
+ size_t sz = strlen(in);
+
+ while (*in) {
+ char c = *(in++);
+
+ switch (c) {
+ case '@':
+ case '.':
+ case '-':
+ case '_':
+ case '/':
+ continue;
+ default:
+ if (isalnum((unsigned char)c))
+ continue;
+ sz += 2;
+ }
+ }
+ return sz;
+}
+
+static char *
+string_encode(const char *in)
+{
+ size_t len = strlen(in);
+ size_t sz = string_encode_sz(in);
+ size_t i, k;
+ char *s;
+
+ if ((s = malloc(sz + 1)) == NULL)
+ return NULL;
+ s[sz] = '\0';
+
+ for (i = k = 0; i < len; i++) {
+ unsigned char c = ((const unsigned char *)in)[i];
+
+ switch (c) {
+ case '@':
+ case '.':
+ case '-':
+ case '_':
+ case '/':
+ s[k++] = c;
+ break;
+ default:
+ if (isalnum(c)) {
+ s[k++] = c;
+ } else {
+ s[k++] = '%';
+ s[k++] = "0123456789abcdef"[(c&0xff)>>4];
+ s[k++] = "0123456789abcdef"[(c&0x0f)];
+ }
+ }
+ }
+ return s;
+}
+
+static int
+cmd_append(struct rk_strpool **cmd, const char *s0, ...)
+{
+ va_list ap;
+ const char *arg;
+ int ret = 0;
+
+ if ((*cmd = rk_strpoolprintf(*cmd, "%s", s0)) == NULL)
+ return ENOMEM;
+
+ va_start(ap, s0);
+ while ((arg = va_arg(ap, const char *))) {
+ char *s;
+
+ if ((s = string_encode(arg)) == NULL) {
+ rk_strpoolfree(*cmd);
+ *cmd = NULL;
+ ret = ENOMEM;
+ goto out;
+ }
+ *cmd = rk_strpoolprintf(*cmd, "%s", s);
+ free(s);
+ if (*cmd == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+ }
+
+ out:
+ va_end(ap);
+ return ret;
+}
+
+/* Like strpbrk(), but from the end of the string */
+static char *
+strrpbrk(char *s, const char *accept)
+{
+ char *last = NULL;
+ char *p = s;
+
+ do {
+ p = strpbrk(p, accept);
+ if (p != NULL) {
+ last = p;
+ p++;
+ }
+ } while (p != NULL);
+ return last;
+}
+
+/*
+ * For /get-tgts we need to support partial authorization of requests. The
+ * hx509_request APIs support that.
+ *
+ * Here we just step through the IPC server's response and mark the
+ * corresponding request elements authorized so that /get-tgts can issue or not
+ * issue TGTs according to which requested principals are authorized and which
+ * are not.
+ */
+static int
+mark_piecemeal_authorized(krb5_context context,
+ hx509_request csr,
+ heim_octet_string *rep)
+{
+ size_t san_idx = 0;
+ size_t eku_idx = 0;
+ char *s, *p, *rep2, *tok, *next = NULL;
+ int slow_path = 0;
+ int partial = 0;
+ int ret = 0;
+
+ /* We have a data, but we want a C string */
+ if ((rep2 = strndup(rep->data, rep->length)) == NULL)
+ return krb5_enomem(context);
+
+ /* The first token should be "denied"; skip it */
+ if ((s = strchr(rep2, ' ')) == NULL) {
+ free(rep2);
+ return EACCES;
+ }
+ s++;
+
+ while ((tok = strtok_r(s, ",", &next))) {
+ hx509_san_type san_type, san_type2;
+ char *s2 = NULL;
+
+ s = NULL; /* for strtok_r() */
+
+ if (strncmp(tok, "eku=", sizeof("eku=") -1) == 0) {
+ /*
+ * Very simplistic handling of partial authz for EKUs:
+ *
+ * - denial of an EKU -> deny the whole request
+ * - else below mark all EKUs approved
+ */
+ if (strstr(tok, ":denied")) {
+ krb5_set_error_message(context, EACCES, "CSR denied because "
+ "EKU denied: %s", tok);
+ ret = EACCES;
+ break;
+ }
+ continue;
+ }
+
+ /*
+ * For SANs we check that the nth SAN in the response matches the nth
+ * SAN in the hx509_request.
+ */
+
+ if (strncmp(tok, "san_pkinit=", sizeof("san_pkinit=") - 1) == 0) {
+ tok += sizeof("san_pkinit=") - 1;
+ san_type = HX509_SAN_TYPE_PKINIT;
+ } else if (strncmp(tok, "san_dnsname=", sizeof("san_dnsname=") -1) == 0) {
+ tok += sizeof("san_dnsname=") - 1;
+ san_type = HX509_SAN_TYPE_DNSNAME;
+ } else if (strncmp(tok, "san_email=", sizeof("san_email=") -1) == 0) {
+ tok += sizeof("san_email=") - 1;
+ san_type = HX509_SAN_TYPE_EMAIL;
+ } else if (strncmp(tok, "san_xmpp=", sizeof("san_xmpp=") -1) == 0) {
+ tok += sizeof("san_xmpp=") - 1;
+ san_type = HX509_SAN_TYPE_XMPP;
+ } else if (strncmp(tok, "san_ms_upn=", sizeof("san_ms_upn=") -1) == 0) {
+ tok += sizeof("san_ms_upn=") - 1;
+ san_type = HX509_SAN_TYPE_MS_UPN;
+ } else {
+ krb5_set_error_message(context, EACCES, "CSR denied because could "
+ "not parse token in response: %s", tok);
+ ret = EACCES;
+ break;
+ }
+
+ /*
+ * This token has to end in ":granted" or ":denied". Using our
+ * `strrpbrk()' means we can deal with principals names that have ':'
+ * in them.
+ */
+ if ((p = strrpbrk(tok, ":")) == NULL) {
+ san_idx++;
+ continue;
+ }
+ *(p++) = '\0';
+
+ /* Now we get the nth SAN from the authorization */
+ ret = hx509_request_get_san(csr, san_idx, &san_type2, &s2);
+ if (ret == HX509_NO_ITEM) {
+ /* See below */
+ slow_path = 1;
+ break;
+ }
+
+ /* And we check that it matches the SAN in this token */
+ if (ret == 0) {
+ if (san_type != san_type2 ||
+ strcmp(tok, s2) != 0) {
+ /*
+ * We expect the tokens in the reply to be in the same order as
+ * in the request. If not, we must take a slow path where we
+ * have to sort requests and responses then iterate them in
+ * order.
+ */
+ slow_path = 1;
+ hx509_xfree(s2);
+ break;
+ }
+ hx509_xfree(s2);
+
+ if (strcmp(p, "granted") == 0) {
+ ret = hx509_request_authorize_san(csr, san_idx);
+ } else {
+ partial = 1;
+ ret = hx509_request_reject_san(csr, san_idx);
+ }
+ if (ret)
+ break;
+ }
+ san_idx++;
+ }
+
+ if (slow_path) {
+ /*
+ * FIXME? Implement the slow path?
+ *
+ * Basically, we'd get all the SANs from the request into an array of
+ * {SAN, index} and sort that array, then all the SANs from the
+ * response into an array and sort it, then step a cursor through both,
+ * using the index from the first to mark SANs in the request
+ * authorized or rejected.
+ */
+ krb5_set_error_message(context, EACCES, "CSR denied because "
+ "authorizer service did not include all "
+ "piecemeal grants/denials in order");
+ ret = EACCES;
+ }
+
+ /* Mark all the EKUs authorized */
+ for (eku_idx = 0; ret == 0; eku_idx++)
+ ret = hx509_request_authorize_eku(csr, eku_idx);
+ if (ret == HX509_NO_ITEM)
+ ret = 0;
+ if (ret == 0 && partial) {
+ krb5_set_error_message(context, EACCES, "CSR partially authorized");
+ ret = EACCES;
+ }
+
+ free(rep2);
+ return ret;
+}
+
+static krb5_error_code mark_authorized(hx509_request);
+
+static int
+call_svc(krb5_context context,
+ heim_ipc ipc,
+ hx509_request csr,
+ const char *cmd,
+ int piecemeal_check_ok)
+{
+ heim_octet_string req, resp;
+ int ret;
+
+ req.data = (void *)(uintptr_t)cmd;
+ req.length = strlen(cmd);
+ resp.length = 0;
+ resp.data = NULL;
+ ret = heim_ipc_call(ipc, &req, &resp, NULL);
+
+ /* Check for all granted case */
+ if (ret == 0 &&
+ resp.length == sizeof("granted") - 1 &&
+ strncasecmp(resp.data, "granted", sizeof("granted") - 1) == 0) {
+ free(resp.data);
+ return mark_authorized(csr); /* Full approval */
+ }
+
+ /* Check for "denied ..." piecemeal authorization case */
+ if ((ret == 0 || ret == EACCES || ret == KRB5_PLUGIN_NO_HANDLE) &&
+ piecemeal_check_ok &&
+ resp.length > sizeof("denied") - 1 &&
+ strncasecmp(resp.data, "denied", sizeof("denied") - 1) == 0) {
+ /* Piecemeal authorization */
+ ret = mark_piecemeal_authorized(context, csr, &resp);
+
+ /* mark_piecemeal_authorized() should return EACCES; just in case: */
+ if (ret == 0)
+ ret = EACCES;
+ free(resp.data);
+ return ret;
+ }
+
+ /* All other failure cases */
+
+ if (resp.data == NULL || resp.length == 0) {
+ krb5_set_error_message(context, ret, "CSR authorizer IPC service "
+ "failed silently");
+ free(resp.data);
+ return EACCES;
+ }
+
+ if (resp.length == sizeof("ignore") - 1 &&
+ strncasecmp(resp.data, "ignore", sizeof("ignore") - 1) == 0) {
+ /*
+ * In this case the server is saying "I can't handle this request, try
+ * some other authorizer plugin".
+ */
+ free(resp.data);
+ return KRB5_PLUGIN_NO_HANDLE;
+ }
+
+ if (resp.length == sizeof("denied") - 1 &&
+ strncasecmp(resp.data, "denied", sizeof("denied") - 1) == 0) {
+ krb5_set_error_message(context, ret, "CSR authorizer rejected %s",
+ cmd);
+ free(resp.data);
+ return EACCES;
+ }
+
+ if (resp.length > INT_MAX)
+ krb5_set_error_message(context, ret, "CSR authorizer rejected %s", cmd);
+ else
+ krb5_set_error_message(context, ret, "CSR authorizer rejected %s: %.*s",
+ cmd, resp.length, resp.data);
+
+ free(resp.data);
+ return ret;
+}
+
+static void
+frees(char **s)
+{
+ free(*s);
+ *s = NULL;
+}
+
+static krb5_error_code
+mark_authorized(hx509_request csr)
+{
+ size_t i;
+ char *s;
+ int ret = 0;
+
+ for (i = 0; ret == 0; i++) {
+ ret = hx509_request_get_eku(csr, i, &s);
+ if (ret == 0)
+ hx509_request_authorize_eku(csr, i);
+ frees(&s);
+ }
+ if (ret == HX509_NO_ITEM)
+ ret = 0;
+
+ for (i = 0; ret == 0; i++) {
+ hx509_san_type san_type;
+ ret = hx509_request_get_san(csr, i, &san_type, &s);
+ if (ret == 0)
+ hx509_request_authorize_san(csr, i);
+ frees(&s);
+ }
+ return ret == HX509_NO_ITEM ? 0 : ret;
+}
+
+static KRB5_LIB_CALL krb5_error_code
+authorize(void *ctx,
+ krb5_context context,
+ const char *app,
+ hx509_request csr,
+ krb5_const_principal client,
+ krb5_boolean *result)
+{
+ struct rk_strpool *cmd = NULL;
+ krb5_error_code ret;
+ hx509_context hx509ctx = NULL;
+ heim_ipc ipc = NULL;
+ const char *svc;
+ KeyUsage ku;
+ size_t i;
+ char *princ = NULL;
+ char *s = NULL;
+ int do_check = 0;
+ int piecemeal_check_ok = 1;
+
+ if ((svc = krb5_config_get_string_default(context, NULL,
+ "ANY:org.h5l.csr_authorizer",
+ app ? app : "kdc",
+ "ipc_csr_authorizer", "service",
+ NULL)) == NULL)
+ return KRB5_PLUGIN_NO_HANDLE;
+
+ if ((ret = heim_ipc_init_context(svc, &ipc))) {
+ /*
+ * If the IPC authorizer is optional, then fallback on whatever is
+ * next.
+ */
+ if (krb5_config_get_bool_default(context, NULL, FALSE,
+ app ? app : "kdc",
+ "ipc_csr_authorizer", "optional",
+ NULL))
+ return KRB5_PLUGIN_NO_HANDLE;
+ krb5_set_error_message(context, ret, "Could not set up IPC client "
+ "end-point for service %s", svc);
+ return ret;
+ }
+
+ if ((ret = hx509_context_init(&hx509ctx)))
+ goto out;
+
+ if ((ret = krb5_unparse_name(context, client, &princ)))
+ goto out;
+
+ if ((ret = cmd_append(&cmd, "check ", princ, NULL)))
+ goto enomem;
+ frees(&princ);
+
+ for (i = 0; ret == 0; i++) {
+ hx509_san_type san_type;
+ size_t p;
+
+ ret = hx509_request_get_san(csr, i, &san_type, &s);
+ if (ret)
+ break;
+
+ /*
+ * We cannot do a piecemeal check if any of the SANs could make the
+ * response ambiguous.
+ */
+ p = strcspn(s, ",= ");
+ if (s[p] != '\0')
+ piecemeal_check_ok = 0;
+ if (piecemeal_check_ok && strstr(s, ":granted") != NULL)
+ piecemeal_check_ok = 0;
+
+ switch (san_type) {
+ case HX509_SAN_TYPE_EMAIL:
+ if ((ret = cmd_append(&cmd, " san_email=", s, NULL)))
+ goto enomem;
+ do_check = 1;
+ break;
+ case HX509_SAN_TYPE_DNSNAME:
+ if ((ret = cmd_append(&cmd, " san_dnsname=", s, NULL)))
+ goto enomem;
+ do_check = 1;
+ break;
+ case HX509_SAN_TYPE_XMPP:
+ if ((ret = cmd_append(&cmd, " san_xmpp=", s, NULL)))
+ goto enomem;
+ do_check = 1;
+ break;
+ case HX509_SAN_TYPE_PKINIT:
+ if ((ret = cmd_append(&cmd, " san_pkinit=", s, NULL)))
+ goto enomem;
+ do_check = 1;
+ break;
+ case HX509_SAN_TYPE_MS_UPN:
+ if ((ret = cmd_append(&cmd, " san_ms_upn=", s, NULL)))
+ goto enomem;
+ do_check = 1;
+ break;
+ default:
+ if ((ret = hx509_request_reject_san(csr, i)))
+ goto out;
+ break;
+ }
+ frees(&s);
+ }
+ if (ret == HX509_NO_ITEM)
+ ret = 0;
+ if (ret)
+ goto out;
+
+ for (i = 0; ret == 0; i++) {
+ ret = hx509_request_get_eku(csr, i, &s);
+ if (ret)
+ break;
+ if ((ret = cmd_append(&cmd, " eku=", s, NULL)))
+ goto enomem;
+ do_check = 1;
+ frees(&s);
+ }
+ if (ret == HX509_NO_ITEM)
+ ret = 0;
+ if (ret)
+ goto out;
+
+ ku = int2KeyUsage(0);
+ ku.digitalSignature = 1;
+ ku.nonRepudiation = 1;
+ hx509_request_authorize_ku(csr, ku);
+
+ if (do_check) {
+ s = rk_strpoolcollect(cmd);
+ cmd = NULL;
+ if (s == NULL)
+ goto enomem;
+ if ((ret = call_svc(context, ipc, csr, s, piecemeal_check_ok)))
+ goto out;
+ } /* else there was nothing to check -> permit */
+
+ *result = TRUE;
+ ret = 0;
+ goto out;
+
+enomem:
+ ret = krb5_enomem(context);
+ goto out;
+
+out:
+ heim_ipc_free_context(ipc);
+ hx509_context_free(&hx509ctx);
+ if (cmd)
+ rk_strpoolfree(cmd);
+ free(princ);
+ free(s);
+ return ret;
+}
+
+static KRB5_LIB_CALL krb5_error_code
+ipc_csr_authorizer_init(krb5_context context, void **c)
+{
+ *c = NULL;
+ return 0;
+}
+
+static KRB5_LIB_CALL void
+ipc_csr_authorizer_fini(void *c)
+{
+}
+
+static krb5plugin_csr_authorizer_ftable plug_desc =
+ { 1, ipc_csr_authorizer_init, ipc_csr_authorizer_fini, authorize };
+
+static krb5plugin_csr_authorizer_ftable *plugs[] = { &plug_desc };
+
+static uintptr_t
+ipc_csr_authorizer_get_instance(const char *libname)
+{
+ if (strcmp(libname, "krb5") == 0)
+ return krb5_get_instance(libname);
+ if (strcmp(libname, "kdc") == 0)
+ return kdc_get_instance(libname);
+ if (strcmp(libname, "hx509") == 0)
+ return hx509_get_instance(libname);
+ return 0;
+}
+
+krb5_plugin_load_ft kdc_csr_authorizer_plugin_load;
+
+krb5_error_code KRB5_CALLCONV
+kdc_csr_authorizer_plugin_load(heim_pcontext context,
+ krb5_get_instance_func_t *get_instance,
+ size_t *num_plugins,
+ krb5_plugin_common_ftable_cp **plugins)
+{
+ *get_instance = ipc_csr_authorizer_get_instance;
+ *num_plugins = sizeof(plugs) / sizeof(plugs[0]);
+ *plugins = (krb5_plugin_common_ftable_cp *)plugs;
+ return 0;
+}
diff --git a/third_party/heimdal/kdc/kdc-accessors.h b/third_party/heimdal/kdc/kdc-accessors.h
new file mode 100644
index 0000000..c00fd49
--- /dev/null
+++ b/third_party/heimdal/kdc/kdc-accessors.h
@@ -0,0 +1,394 @@
+/*
+ * Copyright (c) 2022, PADL Software Pty Ltd.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of PADL Software nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY PADL SOFTWARE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL PADL SOFTWARE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef HEIMDAL_KDC_KDC_ACCESSORS_H
+#define HEIMDAL_KDC_KDC_ACCESSORS_H 1
+
+/* read-only accessor */
+#ifndef _KDC_REQUEST_GET_ACCESSOR
+#define _KDC_REQUEST_GET_ACCESSOR(R, T, f) \
+ KDC_LIB_FUNCTION T KDC_LIB_CALL \
+ kdc_request_get_ ## f(R);
+#endif
+
+#ifndef _KDC_REQUEST_SET_ACCESSOR
+#define _KDC_REQUEST_SET_ACCESSOR(R, T, f) \
+ KDC_LIB_FUNCTION void KDC_LIB_CALL \
+ kdc_request_set_ ## f(R, T);
+#endif
+
+#ifndef KDC_REQUEST_GET_ACCESSOR
+#define KDC_REQUEST_GET_ACCESSOR(T, f) \
+ _KDC_REQUEST_GET_ACCESSOR(kdc_request_t, T, f)
+#endif
+
+#ifndef KDC_REQUEST_SET_ACCESSOR
+#define KDC_REQUEST_SET_ACCESSOR(T, f) \
+ _KDC_REQUEST_SET_ACCESSOR(kdc_request_t, T, f)
+#endif
+
+#ifndef ASTGS_REQUEST_GET_ACCESSOR
+#define ASTGS_REQUEST_GET_ACCESSOR(T, f) \
+ _KDC_REQUEST_GET_ACCESSOR(astgs_request_t, T, f)
+#endif
+
+#ifndef ASTGS_REQUEST_SET_ACCESSOR
+#define ASTGS_REQUEST_SET_ACCESSOR(T, f) \
+ _KDC_REQUEST_SET_ACCESSOR(astgs_request_t, T, f)
+#endif
+
+/* get/set accessor for pointer type */
+#ifndef _KDC_REQUEST_GET_ACCESSOR_PTR
+#define _KDC_REQUEST_GET_ACCESSOR_PTR(R, T, f) \
+ KDC_LIB_FUNCTION const T KDC_LIB_CALL \
+ kdc_request_get_ ## f(R);
+#endif
+
+#ifndef _KDC_REQUEST_SET_ACCESSOR_PTR
+#define _KDC_REQUEST_SET_ACCESSOR_PTR(R, T, t, f) \
+ KDC_LIB_FUNCTION krb5_error_code KDC_LIB_CALL \
+ kdc_request_set_ ## f(R, const T);
+#endif
+
+#ifndef KDC_REQUEST_GET_ACCESSOR_PTR
+#define KDC_REQUEST_GET_ACCESSOR_PTR(T, f) \
+ _KDC_REQUEST_GET_ACCESSOR_PTR(kdc_request_t, T, f)
+#endif
+
+#ifndef KDC_REQUEST_SET_ACCESSOR_PTR
+#define KDC_REQUEST_SET_ACCESSOR_PTR(T, t, f) \
+ _KDC_REQUEST_SET_ACCESSOR_PTR(kdc_request_t, T, t, f)
+#endif
+
+#ifndef ASTGS_REQUEST_GET_ACCESSOR_PTR
+#define ASTGS_REQUEST_GET_ACCESSOR_PTR(T, f) \
+ _KDC_REQUEST_GET_ACCESSOR_PTR(astgs_request_t, T, f)
+#endif
+
+#ifndef ASTGS_REQUEST_SET_ACCESSOR_PTR
+#define ASTGS_REQUEST_SET_ACCESSOR_PTR(T, t, f) \
+ _KDC_REQUEST_SET_ACCESSOR_PTR(astgs_request_t, T, t, f)
+#endif
+
+/* get/set accessor for struct type */
+#ifndef _KDC_REQUEST_GET_ACCESSOR_STRUCT
+#define _KDC_REQUEST_GET_ACCESSOR_STRUCT(R, T, f) \
+ KDC_LIB_FUNCTION const T * KDC_LIB_CALL \
+ kdc_request_get_ ## f(R);
+#endif
+
+#ifndef _KDC_REQUEST_SET_ACCESSOR_STRUCT
+#define _KDC_REQUEST_SET_ACCESSOR_STRUCT(R, T, t, f) \
+ KDC_LIB_FUNCTION krb5_error_code KDC_LIB_CALL \
+ kdc_request_set_ ## f(R, const T *);
+#endif
+
+#ifndef KDC_REQUEST_GET_ACCESSOR_STRUCT
+#define KDC_REQUEST_GET_ACCESSOR_STRUCT(T, f) \
+ _KDC_REQUEST_GET_ACCESSOR_STRUCT(kdc_request_t, T, f)
+#endif
+
+#ifndef KDC_REQUEST_SET_ACCESSOR_STRUCT
+#define KDC_REQUEST_SET_ACCESSOR_STRUCT(T, t, f) \
+ _KDC_REQUEST_SET_ACCESSOR_STRUCT(kdc_request_t, T, t, f)
+#endif
+
+#ifndef ASTGS_REQUEST_GET_ACCESSOR_STRUCT
+#define ASTGS_REQUEST_GET_ACCESSOR_STRUCT(T, f) \
+ _KDC_REQUEST_GET_ACCESSOR_STRUCT(astgs_request_t, T, f)
+#endif
+
+#ifndef ASTGS_REQUEST_SET_ACCESSOR_STRUCT
+#define ASTGS_REQUEST_SET_ACCESSOR_STRUCT(T, t, f) \
+ _KDC_REQUEST_SET_ACCESSOR_STRUCT(astgs_request_t, T, t, f)
+#endif
+
+/*
+ * krb5_context
+ * kdc_request_get_context(kdc_request_t);
+ */
+
+KDC_REQUEST_GET_ACCESSOR(krb5_context, context)
+
+/*
+ * krb5_kdc_configuration *
+ * kdc_request_get_config(kdc_request_t);
+ */
+
+KDC_REQUEST_GET_ACCESSOR(krb5_kdc_configuration *, config)
+
+/*
+ * heim_log_facility *
+ * kdc_request_get_logf(kdc_request_t);
+ */
+
+KDC_REQUEST_GET_ACCESSOR(heim_log_facility *, logf)
+
+/*
+ * const char *
+ * kdc_request_get_from(kdc_request_t);
+ */
+
+KDC_REQUEST_GET_ACCESSOR_PTR(char *, from)
+
+/*
+ * const struct sockaddr *
+ * kdc_request_get_addr(kdc_request_t);
+ */
+
+KDC_REQUEST_GET_ACCESSOR_PTR(struct sockaddr *, addr)
+
+/*
+ * krb5_data
+ * kdc_request_get_request(kdc_request_t);
+ */
+
+KDC_REQUEST_GET_ACCESSOR(krb5_data, request)
+
+/*
+ * struct timeval
+ * kdc_request_get_tv_start(kdc_request_t);
+ */
+
+KDC_REQUEST_GET_ACCESSOR(struct timeval, tv_start)
+
+/*
+ * struct timeval
+ * kdc_request_get_tv_end(kdc_request_t);
+ */
+
+KDC_REQUEST_GET_ACCESSOR(struct timeval, tv_end)
+
+/*
+ * krb5_error_code
+ * kdc_request_get_error_code(kdc_request_t);
+ */
+KDC_REQUEST_GET_ACCESSOR(krb5_error_code, error_code)
+
+/*
+ * void
+ * kdc_request_set_error_code(kdc_request_t, krb5_error_code);
+ */
+KDC_REQUEST_SET_ACCESSOR(krb5_error_code, error_code)
+
+/*
+ * const KDC_REQ *
+ * kdc_request_get_req(astgs_request_t);
+ */
+
+ASTGS_REQUEST_GET_ACCESSOR_STRUCT(KDC_REQ, req)
+
+/*
+ * const KDC_REP *
+ * kdc_request_get_rep(astgs_request_t);
+ */
+
+ASTGS_REQUEST_GET_ACCESSOR_STRUCT(KDC_REP, rep)
+
+/*
+ * krb5_error_code
+ * kdc_request_set_rep(astgs_request_t, const KDC_REP *);
+ */
+
+ASTGS_REQUEST_SET_ACCESSOR_STRUCT(KDC_REP, KDC_REP, rep)
+
+/*
+ * const char *
+ * kdc_request_get_cname(kdc_request_t);
+ */
+
+KDC_REQUEST_GET_ACCESSOR_PTR(char *, cname)
+
+/*
+ * krb5_error_code
+ * kdc_request_set_cname(kdc_request_t, const char *);
+ */
+
+KDC_REQUEST_SET_ACCESSOR_PTR(char *, string_ptr, cname)
+
+/*
+ * const Principal *
+ * kdc_request_get_client_princ(astgs_request_t);
+ */
+
+ASTGS_REQUEST_GET_ACCESSOR_PTR(Principal *, client_princ)
+
+/*
+ * krb5_error_code
+ * kdc_request_set_client_princ(astgs_request_t, const Principal *);
+ */
+
+ASTGS_REQUEST_SET_ACCESSOR_PTR(Principal *, Principal_ptr, client_princ)
+
+/*
+ * const Principal *
+ * kdc_request_get_canon_client_princ(astgs_request_t);
+ */
+
+ASTGS_REQUEST_GET_ACCESSOR_PTR(Principal *, canon_client_princ)
+
+/*
+ * krb5_error_code
+ * kdc_request_set_canon_client_princ(astgs_request_t, const Principal *);
+ */
+
+ASTGS_REQUEST_SET_ACCESSOR_PTR(Principal *, Principal_ptr, canon_client_princ)
+
+/*
+ * const HDB *
+ * kdc_request_get_clientdb(astgs_request_t);
+ */
+
+ASTGS_REQUEST_GET_ACCESSOR_PTR(HDB *, clientdb)
+
+/*
+ * const hdb_entry *
+ * kdc_request_get_client(astgs_request_t);
+ */
+
+ASTGS_REQUEST_GET_ACCESSOR_PTR(hdb_entry *, client)
+
+/*
+ * See client accessors
+ */
+
+KDC_REQUEST_GET_ACCESSOR_PTR(char *, sname)
+KDC_REQUEST_SET_ACCESSOR_PTR(char *, string_ptr, sname)
+ASTGS_REQUEST_GET_ACCESSOR_PTR(Principal *, server_princ)
+ASTGS_REQUEST_SET_ACCESSOR_PTR(Principal *, Principal_ptr, server_princ)
+ASTGS_REQUEST_GET_ACCESSOR_PTR(HDB *, serverdb)
+ASTGS_REQUEST_GET_ACCESSOR_PTR(hdb_entry *, server)
+
+/*
+ * See client accessors
+ */
+
+ASTGS_REQUEST_GET_ACCESSOR_PTR(Principal *, krbtgt_princ)
+ASTGS_REQUEST_SET_ACCESSOR_PTR(Principal *, Principal_ptr, krbtgt_princ)
+ASTGS_REQUEST_GET_ACCESSOR_PTR(HDB *, krbtgtdb)
+ASTGS_REQUEST_GET_ACCESSOR_PTR(hdb_entry *, krbtgt)
+
+/*
+ * krb5_ticket *
+ * kdc_request_get_ticket(astgs_request_t);
+ */
+
+ASTGS_REQUEST_GET_ACCESSOR(krb5_ticket *, ticket)
+
+/*
+ * const krb5_keyblock *
+ * kdc_request_get_reply_key(astgs_request_t);
+ */
+
+ASTGS_REQUEST_GET_ACCESSOR_STRUCT(krb5_keyblock, reply_key)
+
+/*
+ * krb5_error_code
+ * kdc_request_set_reply_key(astgs_request_t, const krb5_keyblock *);
+ */
+
+ASTGS_REQUEST_SET_ACCESSOR_STRUCT(krb5_keyblock, keyblock, reply_key)
+
+/*
+ * krb5_const_pac
+ * kdc_request_get_pac(astgs_request_t);
+ */
+
+ASTGS_REQUEST_GET_ACCESSOR_PTR(struct krb5_pac_data *, pac)
+
+/*
+ * krb5_error_code
+ * kdc_request_set_pac(astgs_request_t, krb5_const_pac);
+ */
+
+ASTGS_REQUEST_SET_ACCESSOR_PTR(struct krb5_pac_data *, pac, pac)
+
+/*
+ * uint64_t
+ * kdc_request_get_pac_attributes(astgs_request_t);
+ */
+
+ASTGS_REQUEST_GET_ACCESSOR(uint64_t, pac_attributes)
+
+/*
+ * void
+ * kdc_request_set_pac_attributes(astgs_request_t, uint64_t);
+ */
+
+ASTGS_REQUEST_SET_ACCESSOR(uint64_t, pac_attributes)
+
+KDC_LIB_FUNCTION const HDB * KDC_LIB_CALL
+kdc_request_get_explicit_armor_clientdb(astgs_request_t);
+
+KDC_LIB_FUNCTION const hdb_entry * KDC_LIB_CALL
+kdc_request_get_explicit_armor_client(astgs_request_t);
+
+KDC_LIB_FUNCTION const hdb_entry * KDC_LIB_CALL
+kdc_request_get_explicit_armor_server(astgs_request_t);
+
+KDC_LIB_FUNCTION krb5_const_pac KDC_LIB_CALL
+kdc_request_get_explicit_armor_pac(astgs_request_t);
+
+/*
+ * const HDB *
+ * kdc_request_get_armor_clientdb(astgs_request_t);
+ */
+
+ASTGS_REQUEST_GET_ACCESSOR_PTR(HDB *, armor_clientdb)
+
+/*
+ * const hdb_entry *
+ * kdc_request_get_armor_client(astgs_request_t);
+ */
+ASTGS_REQUEST_GET_ACCESSOR_PTR(hdb_entry *, armor_client);
+
+/*
+ * const hdb_entry *
+ * kdc_request_get_armor_server(astgs_request_t);
+ */
+ASTGS_REQUEST_GET_ACCESSOR_PTR(hdb_entry *, armor_server);
+
+/*
+ * krb5_const_pac
+ * kdc_request_get_armor_pac(astgs_request_t);
+ */
+
+ASTGS_REQUEST_GET_ACCESSOR_PTR(struct krb5_pac_data *, armor_pac);
+
+/*
+ * krb5_boolean
+ * kdc_request_get_explicit_armor_present(astgs_request_t);
+ */
+
+ASTGS_REQUEST_GET_ACCESSOR_PTR(krb5_boolean, explicit_armor_present);
+
+#endif /* HEIMDAL_KDC_KDC_ACCESSORS_H */
diff --git a/third_party/heimdal/kdc/kdc-audit.h b/third_party/heimdal/kdc/kdc-audit.h
new file mode 100644
index 0000000..4097c48
--- /dev/null
+++ b/third_party/heimdal/kdc/kdc-audit.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2006 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* $Id$ */
+
+#ifndef HEIMDAL_KDC_KDC_AUDIT_H
+#define HEIMDAL_KDC_KDC_AUDIT_H 1
+
+/*
+ * KDC auditing
+ */
+
+/* auth event type enumeration, currently for AS only */
+#define KDC_AUTH_EVENT_INVALID 0 /* no event logged */
+#define KDC_AUTH_EVENT_CLIENT_AUTHORIZED 1 /* all authn/authz checks passed */
+#define KDC_AUTH_EVENT_CLIENT_UNKNOWN 2 /* client unknown */
+#define KDC_AUTH_EVENT_CLIENT_LOCKED_OUT 3 /* client locked out */
+#define KDC_AUTH_EVENT_CLIENT_TIME_SKEW 4 /* client time skew */
+#define KDC_AUTH_EVENT_WRONG_LONG_TERM_KEY 5 /* PA failed to validate long term key */
+#define KDC_AUTH_EVENT_VALIDATED_LONG_TERM_KEY 6 /* PA validated long term key */
+#define KDC_AUTH_EVENT_CLIENT_NAME_UNAUTHORIZED 7 /* couldn't map GSS/PKINIT name to principal */
+#define KDC_AUTH_EVENT_PREAUTH_FAILED 8 /* generic PA failure */
+#define KDC_AUTH_EVENT_PREAUTH_SUCCEEDED 9 /* generic (non-long term key) PA success */
+#define KDC_AUTH_EVENT_HISTORIC_LONG_TERM_KEY 10 /* PA failed to validate current long term key, but historic */
+#define KDC_AUTH_EVENT_CLIENT_FOUND 11 /* the client was successfully looked up */
+
+/*
+ * Audit keys to be queried using kdc_audit_getkv(). There are other keys
+ * intended for logging that are not defined below; the constants below are
+ * there to ease migration from the older auth_status HDB API.
+ */
+
+#define KDC_REQUEST_KV_AUTH_EVENT "#auth_event" /* heim_number_t */
+#define KDC_REQUEST_KV_PA_NAME "pa" /* heim_string_t */
+#define KDC_REQUEST_KV_PA_ETYPE "pa-etype" /* heim_number_t */
+#define KDC_REQUEST_KV_PA_SUCCEEDED_KVNO "pa-succeeded-kvno" /* heim_number_t */
+#define KDC_REQUEST_KV_PA_FAILED_KVNO "pa-failed-kvno" /* heim_number_t */
+#define KDC_REQUEST_KV_GSS_INITIATOR "gss_initiator" /* heim_string_t */
+#define KDC_REQUEST_KV_PKINIT_CLIENT_CERT "pkinit_client_cert" /* heim_string_t */
+#define KDC_REQUEST_KV_PA_HISTORIC_KVNO "pa-historic-kvno" /* heim_number_t */
+
+#endif /* HEIMDAL_KDC_KDC_AUDIT_H */
diff --git a/third_party/heimdal/kdc/kdc-plugin.c b/third_party/heimdal/kdc/kdc-plugin.c
new file mode 100644
index 0000000..3b065c6
--- /dev/null
+++ b/third_party/heimdal/kdc/kdc-plugin.c
@@ -0,0 +1,753 @@
+/*
+ * Copyright (c) 2007 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Portions (c) 2021, 2022 PADL Software Pty Ltd.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "kdc_locl.h"
+
+static int have_plugin = 0;
+
+/*
+ * Pick the first KDC plugin module that we find.
+ */
+
+static const char *kdc_plugin_deps[] = {
+ "kdc",
+ "krb5",
+ "hdb",
+ NULL
+};
+
+static struct heim_plugin_data kdc_plugin_data = {
+ "krb5",
+ "kdc",
+ KRB5_PLUGIN_KDC_VERSION_11,
+ kdc_plugin_deps,
+ kdc_get_instance
+};
+
+static krb5_error_code KRB5_LIB_CALL
+load(krb5_context context, const void *plug, void *plugctx, void *userctx)
+{
+ have_plugin = 1;
+ return KRB5_PLUGIN_NO_HANDLE;
+}
+
+KDC_LIB_FUNCTION krb5_error_code KDC_LIB_CALL
+krb5_kdc_plugin_init(krb5_context context)
+{
+ (void)_krb5_plugin_run_f(context, &kdc_plugin_data, 0, NULL, load);
+
+ return 0;
+}
+
+struct generate_uc {
+ astgs_request_t r;
+ hdb_entry *client;
+ hdb_entry *server;
+ const krb5_keyblock *reply_key;
+ uint64_t pac_attributes;
+ krb5_pac *pac;
+};
+
+static krb5_error_code KRB5_LIB_CALL
+generate(krb5_context context, const void *plug, void *plugctx, void *userctx)
+{
+ const krb5plugin_kdc_ftable *ft = (const krb5plugin_kdc_ftable *)plug;
+ struct generate_uc *uc = (struct generate_uc *)userctx;
+
+ if (ft->pac_generate == NULL)
+ return KRB5_PLUGIN_NO_HANDLE;
+
+ return ft->pac_generate((void *)plug,
+ uc->r,
+ uc->client,
+ uc->server,
+ uc->reply_key,
+ uc->pac_attributes,
+ uc->pac);
+}
+
+
+krb5_error_code
+_kdc_pac_generate(astgs_request_t r,
+ hdb_entry *client,
+ hdb_entry *server,
+ const krb5_keyblock *reply_key,
+ uint64_t pac_attributes,
+ krb5_pac *pac)
+{
+ krb5_error_code ret = 0;
+ struct generate_uc uc;
+
+ *pac = NULL;
+
+ if (krb5_config_get_bool_default(r->context, NULL, FALSE, "realms",
+ client->principal->realm,
+ "disable_pac", NULL))
+ return 0;
+
+ if (have_plugin) {
+ uc.r = r;
+ uc.client = client;
+ uc.server = server;
+ uc.reply_key = reply_key;
+ uc.pac = pac;
+ uc.pac_attributes = pac_attributes;
+
+ ret = _krb5_plugin_run_f(r->context, &kdc_plugin_data,
+ 0, &uc, generate);
+ if (ret != KRB5_PLUGIN_NO_HANDLE)
+ return ret;
+ ret = 0;
+ }
+
+ if (*pac == NULL)
+ ret = krb5_pac_init(r->context, pac);
+
+ return ret;
+}
+
+struct verify_uc {
+ astgs_request_t r;
+ krb5_const_principal client_principal;
+ hdb_entry *delegated_proxy;
+ hdb_entry *client;
+ hdb_entry *server;
+ hdb_entry *krbtgt;
+ EncTicketPart *ticket;
+ krb5_pac pac;
+};
+
+static krb5_error_code KRB5_LIB_CALL
+verify(krb5_context context, const void *plug, void *plugctx, void *userctx)
+{
+ const krb5plugin_kdc_ftable *ft = (const krb5plugin_kdc_ftable *)plug;
+ struct verify_uc *uc = (struct verify_uc *)userctx;
+ krb5_error_code ret;
+
+ if (ft->pac_verify == NULL)
+ return KRB5_PLUGIN_NO_HANDLE;
+
+ ret = ft->pac_verify((void *)plug,
+ uc->r,
+ uc->client_principal,
+ uc->delegated_proxy,
+ uc->client, uc->server, uc->krbtgt,
+ uc->ticket, uc->pac);
+ return ret;
+}
+
+krb5_error_code
+_kdc_pac_verify(astgs_request_t r,
+ krb5_const_principal client_principal,
+ hdb_entry *delegated_proxy,
+ hdb_entry *client,
+ hdb_entry *server,
+ hdb_entry *krbtgt,
+ EncTicketPart *ticket,
+ krb5_pac pac)
+{
+ struct verify_uc uc;
+
+ if (!have_plugin)
+ return KRB5_PLUGIN_NO_HANDLE;
+
+ uc.r = r;
+ uc.client_principal = client_principal;
+ uc.delegated_proxy = delegated_proxy;
+ uc.client = client;
+ uc.server = server;
+ uc.krbtgt = krbtgt;
+ uc.ticket = ticket,
+ uc.pac = pac;
+
+ return _krb5_plugin_run_f(r->context, &kdc_plugin_data,
+ 0, &uc, verify);
+}
+
+struct update_uc {
+ astgs_request_t r;
+ krb5_const_principal client_principal;
+ hdb_entry *delegated_proxy;
+ krb5_const_pac delegated_proxy_pac;
+ hdb_entry *client;
+ hdb_entry *server;
+ hdb_entry *krbtgt;
+ krb5_pac *pac;
+};
+
+static krb5_error_code KRB5_LIB_CALL
+update(krb5_context context, const void *plug, void *plugctx, void *userctx)
+{
+ const krb5plugin_kdc_ftable *ft = (const krb5plugin_kdc_ftable *)plug;
+ struct update_uc *uc = (struct update_uc *)userctx;
+ krb5_error_code ret;
+
+ if (ft->pac_update == NULL)
+ return KRB5_PLUGIN_NO_HANDLE;
+
+ ret = ft->pac_update((void *)plug,
+ uc->r,
+ uc->client_principal,
+ uc->delegated_proxy,
+ uc->delegated_proxy_pac,
+ uc->client, uc->server, uc->krbtgt, uc->pac);
+ return ret;
+}
+
+krb5_error_code
+_kdc_pac_update(astgs_request_t r,
+ krb5_const_principal client_principal,
+ hdb_entry *delegated_proxy,
+ krb5_const_pac delegated_proxy_pac,
+ hdb_entry *client,
+ hdb_entry *server,
+ hdb_entry *krbtgt,
+ krb5_pac *pac)
+{
+ struct update_uc uc;
+
+ if (!have_plugin)
+ return KRB5_PLUGIN_NO_HANDLE;
+
+ uc.r = r;
+ uc.client_principal = client_principal;
+ uc.delegated_proxy = delegated_proxy;
+ uc.delegated_proxy_pac = delegated_proxy_pac;
+ uc.client = client;
+ uc.server = server;
+ uc.krbtgt = krbtgt;
+ uc.pac = pac;
+
+ return _krb5_plugin_run_f(r->context, &kdc_plugin_data,
+ 0, &uc, update);
+}
+
+static krb5_error_code KRB5_LIB_CALL
+check(krb5_context context, const void *plug, void *plugctx, void *userctx)
+{
+ const krb5plugin_kdc_ftable *ft = (const krb5plugin_kdc_ftable *)plug;
+
+ if (ft->client_access == NULL)
+ return KRB5_PLUGIN_NO_HANDLE;
+ return ft->client_access((void *)plug, userctx);
+}
+
+krb5_error_code
+_kdc_check_access(astgs_request_t r)
+{
+ krb5_error_code ret = KRB5_PLUGIN_NO_HANDLE;
+
+ if (have_plugin) {
+ ret = _krb5_plugin_run_f(r->context, &kdc_plugin_data,
+ 0, r, check);
+ }
+
+ if (ret == KRB5_PLUGIN_NO_HANDLE)
+ return kdc_check_flags(r, r->req.msg_type == krb_as_req,
+ r->client, r->server);
+ return ret;
+}
+
+static krb5_error_code KRB5_LIB_CALL
+referral_policy(krb5_context context, const void *plug, void *plugctx, void *userctx)
+{
+ const krb5plugin_kdc_ftable *ft = (const krb5plugin_kdc_ftable *)plug;
+
+ if (ft->referral_policy == NULL)
+ return KRB5_PLUGIN_NO_HANDLE;
+ return ft->referral_policy((void *)plug, userctx);
+}
+
+krb5_error_code
+_kdc_referral_policy(astgs_request_t r)
+{
+ krb5_error_code ret = KRB5_PLUGIN_NO_HANDLE;
+
+ if (have_plugin)
+ ret = _krb5_plugin_run_f(r->context, &kdc_plugin_data, 0, r, referral_policy);
+
+ return ret;
+}
+
+static krb5_error_code KRB5_LIB_CALL
+finalize_reply(krb5_context context, const void *plug, void *plugctx, void *userctx)
+{
+ const krb5plugin_kdc_ftable *ft = (const krb5plugin_kdc_ftable *)plug;
+
+ if (ft->finalize_reply == NULL)
+ return KRB5_PLUGIN_NO_HANDLE;
+ return ft->finalize_reply((void *)plug, userctx);
+}
+
+krb5_error_code
+_kdc_finalize_reply(astgs_request_t r)
+{
+ krb5_error_code ret = KRB5_PLUGIN_NO_HANDLE;
+
+ if (have_plugin)
+ ret = _krb5_plugin_run_f(r->context, &kdc_plugin_data, 0, r, finalize_reply);
+
+ if (ret == KRB5_PLUGIN_NO_HANDLE)
+ ret = 0;
+
+ return ret;
+}
+
+static krb5_error_code KRB5_LIB_CALL
+audit(krb5_context context, const void *plug, void *plugctx, void *userctx)
+{
+ const krb5plugin_kdc_ftable *ft = (const krb5plugin_kdc_ftable *)plug;
+
+ if (ft->audit == NULL)
+ return KRB5_PLUGIN_NO_HANDLE;
+ return ft->audit((void *)plug, userctx);
+}
+
+krb5_error_code
+_kdc_plugin_audit(astgs_request_t r)
+{
+ krb5_error_code ret = KRB5_PLUGIN_NO_HANDLE;
+
+ if (have_plugin)
+ ret = _krb5_plugin_run_f(r->context, &kdc_plugin_data, 0, r, audit);
+
+ if (ret == KRB5_PLUGIN_NO_HANDLE)
+ ret = 0;
+
+ return ret;
+}
+
+KDC_LIB_FUNCTION uintptr_t KDC_LIB_CALL
+kdc_get_instance(const char *libname)
+{
+ static const char *instance = "libkdc";
+
+ if (strcmp(libname, "kdc") == 0)
+ return (uintptr_t)instance;
+ else if (strcmp(libname, "hdb") == 0)
+ return hdb_get_instance(libname);
+ else if (strcmp(libname, "krb5") == 0)
+ return krb5_get_instance(libname);
+
+ return 0;
+}
+
+/*
+ * Minimum API surface wrapper for libheimbase object types so it
+ * may remain a private interface, yet plugins can interact with
+ * objects.
+ */
+
+KDC_LIB_FUNCTION kdc_object_t KDC_LIB_CALL
+kdc_object_alloc(size_t size, const char *name, kdc_type_dealloc dealloc)
+{
+ return heim_alloc(size, name, dealloc);
+}
+
+KDC_LIB_FUNCTION kdc_object_t KDC_LIB_CALL
+kdc_object_retain(kdc_object_t o)
+{
+ return heim_retain(o);
+}
+
+KDC_LIB_FUNCTION void KDC_LIB_CALL
+kdc_object_release(kdc_object_t o)
+{
+ heim_release(o);
+}
+
+KDC_LIB_FUNCTION kdc_object_t KDC_LIB_CALL
+kdc_bool_create(krb5_boolean v)
+{
+ return heim_bool_create(v);
+}
+
+KDC_LIB_FUNCTION krb5_boolean KDC_LIB_CALL
+kdc_bool_get_value(kdc_object_t o)
+{
+ return heim_bool_val(o);
+}
+
+struct kdc_array_iterator_trampoline_data {
+ kdc_array_iterator_t iter;
+ void *data;
+};
+
+/*
+ * Calling convention shim to avoid needing to update all internal
+ * consumers of heim_array_iterate_f()
+ */
+static void
+_kdc_array_iterator_trampoline(kdc_object_t o, void *data, int *stop)
+{
+ struct kdc_array_iterator_trampoline_data *t = data;
+
+ t->iter(o, t->data, stop);
+}
+
+KDC_LIB_FUNCTION void KDC_LIB_CALL
+kdc_array_iterate(kdc_array_t a, void *d, kdc_array_iterator_t iter)
+{
+ struct kdc_array_iterator_trampoline_data t;
+
+ t.iter = iter;
+ t.data = d;
+
+ heim_array_iterate_f((heim_array_t)a, &t, _kdc_array_iterator_trampoline);
+}
+
+KDC_LIB_FUNCTION size_t KDC_LIB_CALL
+kdc_array_get_length(kdc_array_t a)
+{
+ return heim_array_get_length((heim_array_t)a);
+}
+
+KDC_LIB_FUNCTION kdc_object_t KDC_LIB_CALL
+kdc_array_get_value(heim_array_t a, size_t i)
+{
+ return heim_array_get_value((heim_array_t)a, i);
+}
+
+KDC_LIB_FUNCTION kdc_object_t KDC_LIB_CALL
+kdc_array_copy_value(heim_array_t a, size_t i)
+{
+ return heim_array_copy_value((heim_array_t)a, i);
+}
+
+KDC_LIB_FUNCTION kdc_string_t KDC_LIB_CALL
+kdc_string_create(const char *s)
+{
+ return (kdc_string_t)heim_string_create(s);
+}
+
+KDC_LIB_FUNCTION const char * KDC_LIB_CALL
+kdc_string_get_utf8(kdc_string_t s)
+{
+ return heim_string_get_utf8((heim_string_t)s);
+}
+
+KDC_LIB_FUNCTION kdc_data_t
+kdc_data_create(const void *d, size_t len)
+{
+ return (kdc_data_t)heim_data_create(d, len);
+}
+
+KDC_LIB_FUNCTION const krb5_data * KDC_LIB_CALL
+kdc_data_get_data(kdc_data_t d)
+{
+ return heim_data_get_data((heim_data_t)d);
+}
+
+KDC_LIB_FUNCTION kdc_number_t KDC_LIB_CALL
+kdc_number_create(int64_t v)
+{
+ return (kdc_number_t)heim_number_create(v);
+}
+
+KDC_LIB_FUNCTION int64_t KDC_LIB_CALL
+kdc_number_get_value(kdc_number_t n)
+{
+ return heim_number_get_long((heim_number_t)n);
+}
+
+/*
+ * Plugin accessors
+ */
+
+KDC_LIB_FUNCTION krb5_error_code KDC_LIB_CALL
+kdc_request_add_reply_padata(astgs_request_t r, PA_DATA *md)
+{
+ heim_assert(r->rep.padata != NULL, "reply padata not allocated");
+ return add_METHOD_DATA(r->rep.padata, md);
+}
+
+KDC_LIB_FUNCTION krb5_error_code KDC_LIB_CALL
+kdc_request_add_encrypted_padata(astgs_request_t r, PA_DATA *md)
+{
+ if (r->ek.encrypted_pa_data == NULL) {
+ r->ek.encrypted_pa_data = calloc(1, sizeof *(r->ek.encrypted_pa_data));
+ if (r->ek.encrypted_pa_data == NULL) {
+ return ENOMEM;
+ }
+ }
+
+ return add_METHOD_DATA(r->ek.encrypted_pa_data, md);
+}
+
+KDC_LIB_FUNCTION krb5_error_code KDC_LIB_CALL
+kdc_request_add_pac_buffer(astgs_request_t r,
+ uint32_t pactype,
+ const krb5_data *d)
+{
+ krb5_error_code ret;
+ krb5_pac pac;
+
+ if (r->pac == NULL) {
+ ret = krb5_pac_init(r->context, &pac);
+ if (ret)
+ return ret;
+ } else
+ pac = heim_retain(r->pac);
+
+ ret = krb5_pac_add_buffer(r->context, pac, pactype, d);
+ if (ret == 0 && r->pac == NULL)
+ r->pac = pac;
+ else
+ heim_release(pac);
+
+ return ret;
+}
+
+/*
+ * Override the e-data field to be returned in an error reply. The data will be
+ * owned by the KDC and eventually will be freed with krb5_data_free().
+ */
+KDC_LIB_FUNCTION krb5_error_code KDC_LIB_CALL
+kdc_request_set_e_data(astgs_request_t r, heim_octet_string e_data)
+{
+ krb5_data_free(&r->e_data);
+ r->e_data = e_data;
+
+ return 0;
+}
+
+#undef _KDC_REQUEST_GET_ACCESSOR
+#define _KDC_REQUEST_GET_ACCESSOR(R, T, f) \
+ KDC_LIB_FUNCTION T KDC_LIB_CALL \
+ kdc_request_get_ ## f(R r) \
+ { \
+ return r->f; \
+ }
+
+#undef _KDC_REQUEST_SET_ACCESSOR
+#define _KDC_REQUEST_SET_ACCESSOR(R, T, f) \
+ KDC_LIB_FUNCTION void KDC_LIB_CALL \
+ kdc_request_set_ ## f(R r, T v) \
+ { \
+ r->f = v; \
+ }
+
+#undef _KDC_REQUEST_GET_ACCESSOR_PTR
+#define _KDC_REQUEST_GET_ACCESSOR_PTR(R, T, f) \
+ KDC_LIB_FUNCTION const T KDC_LIB_CALL \
+ kdc_request_get_ ## f(R r) \
+ { \
+ return r->f; \
+ }
+
+#undef _KDC_REQUEST_SET_ACCESSOR_PTR
+#define _KDC_REQUEST_SET_ACCESSOR_PTR(R, T, t, f) \
+ KDC_LIB_FUNCTION krb5_error_code KDC_LIB_CALL \
+ kdc_request_set_ ## f(R r, const T v) \
+ { \
+ krb5_error_code ret; \
+ T tmp; \
+ \
+ if (v == r->f) \
+ return 0; \
+ else if (v) { \
+ ret = copy_##t(v, &tmp); \
+ if (ret) \
+ return ret; \
+ } else \
+ tmp = NULL; \
+ \
+ free_##t(r->f); \
+ r->f = tmp; \
+ \
+ return 0; \
+ }
+
+#undef _KDC_REQUEST_GET_ACCESSOR_STRUCT
+#define _KDC_REQUEST_GET_ACCESSOR_STRUCT(R, T, f) \
+ KDC_LIB_FUNCTION const T * KDC_LIB_CALL \
+ kdc_request_get_ ## f(R r) \
+ { \
+ return &r->f; \
+ }
+
+#undef _KDC_REQUEST_SET_ACCESSOR_STRUCT
+#define _KDC_REQUEST_SET_ACCESSOR_STRUCT(R, T, t, f) \
+ KDC_LIB_FUNCTION krb5_error_code KDC_LIB_CALL \
+ kdc_request_set_ ## f(R r, const T *v) \
+ { \
+ krb5_error_code ret; \
+ T tmp; \
+ \
+ if (v == NULL) \
+ return EINVAL; \
+ else if (v == &r->f) \
+ return 0; \
+ \
+ ret = copy_##t(v, &tmp); \
+ if (ret) \
+ return ret; \
+ \
+ free_##t(&r->f); \
+ r->f = tmp; \
+ \
+ return 0; \
+ }
+
+static krb5_error_code
+copy_string_ptr(const char *src, char **dst)
+{
+ *dst = strdup(src);
+ if (*dst == NULL)
+ return ENOMEM;
+
+ return 0;
+}
+
+static void
+free_string_ptr(char *s)
+{
+ free(s);
+}
+
+static krb5_error_code
+copy_Principal_ptr(krb5_const_principal src, krb5_principal *dst)
+{
+ krb5_error_code ret;
+ krb5_principal p;
+
+ *dst = NULL;
+
+ p = calloc(1, sizeof(*p));
+ if (p == NULL)
+ return ENOMEM;
+
+ ret = copy_Principal(src, p);
+ if (ret == 0)
+ *dst = p;
+ else
+ free(p);
+
+ return ret;
+}
+
+static void
+free_Principal_ptr(krb5_principal p)
+{
+ if (p) {
+ free_Principal(p);
+ free(p);
+ }
+}
+
+static krb5_error_code
+copy_pac(const struct krb5_pac_data *src, struct krb5_pac_data **dst)
+{
+ /* FIXME use heim_copy() when it exists */
+ *dst = (krb5_pac)heim_retain((heim_object_t)src);
+ return 0;
+}
+
+static void
+free_pac(struct krb5_pac_data *o)
+{
+ heim_release(o);
+}
+
+static krb5_error_code
+copy_keyblock(const EncryptionKey *src, EncryptionKey *dst)
+{
+ return copy_EncryptionKey(src, dst);
+}
+
+static void
+free_keyblock(EncryptionKey *key)
+{
+ krb5_free_keyblock_contents(NULL, key);
+}
+
+#undef HEIMDAL_KDC_KDC_ACCESSORS_H
+#include "kdc-accessors.h"
+
+#undef _KDC_REQUEST_GET_ACCESSOR
+#undef _KDC_REQUEST_SET_ACCESSOR
+
+#undef _KDC_REQUEST_GET_ACCESSOR_PTR
+#undef _KDC_REQUEST_SET_ACCESSOR_PTR
+#define _KDC_REQUEST_SET_ACCESSOR_PTR(R, T, t, f) \
+ void \
+ _kdc_request_set_ ## f ## _nocopy(R r, T *v) \
+ { \
+ if (*v != r->f) { \
+ free_##t(r->f); \
+ r->f = *v; \
+ } \
+ *v = NULL; \
+ }
+
+#undef _KDC_REQUEST_GET_ACCESSOR_STRUCT
+#undef _KDC_REQUEST_SET_ACCESSOR_STRUCT
+#define _KDC_REQUEST_SET_ACCESSOR_STRUCT(R, T, t, f) \
+ void \
+ _kdc_request_set_ ## f ## _nocopy(R r, T *v) \
+ { \
+ if (v != &r->f) { \
+ free_##t(&r->f); \
+ r->f = *v; \
+ } \
+ memset(v, 0, sizeof(*v)); \
+ }
+
+#undef HEIMDAL_KDC_KDC_ACCESSORS_H
+#include "kdc-accessors.h"
+
+KDC_LIB_FUNCTION const HDB * KDC_LIB_CALL
+kdc_request_get_explicit_armor_clientdb(astgs_request_t r)
+{
+ return r->explicit_armor_present ? r->armor_clientdb : NULL;
+}
+
+KDC_LIB_FUNCTION const hdb_entry * KDC_LIB_CALL
+kdc_request_get_explicit_armor_client(astgs_request_t r)
+{
+ return r->explicit_armor_present ? r->armor_client : NULL;
+}
+
+KDC_LIB_FUNCTION const hdb_entry * KDC_LIB_CALL
+kdc_request_get_explicit_armor_server(astgs_request_t r)
+{
+ return r->explicit_armor_present ? r->armor_server : NULL;
+}
+
+KDC_LIB_FUNCTION krb5_const_pac KDC_LIB_CALL
+kdc_request_get_explicit_armor_pac(astgs_request_t r)
+{
+ return r->explicit_armor_present ? r->armor_pac : NULL;
+}
diff --git a/third_party/heimdal/kdc/kdc-plugin.h b/third_party/heimdal/kdc/kdc-plugin.h
new file mode 100644
index 0000000..5361349
--- /dev/null
+++ b/third_party/heimdal/kdc/kdc-plugin.h
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2006 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* $Id$ */
+
+#ifndef HEIMDAL_KDC_KDC_PLUGIN_H
+#define HEIMDAL_KDC_KDC_PLUGIN_H 1
+
+#include <krb5.h>
+#include <kdc.h>
+#include <kdc-accessors.h>
+#include <hdb.h>
+
+/*
+ * Allocate a PAC for the given client with krb5_pac_init(),
+ * and fill its contents in with krb5_pac_add_buffer().
+ */
+
+typedef krb5_error_code
+(KRB5_CALLCONV *krb5plugin_kdc_pac_generate)(void *,
+ astgs_request_t,
+ hdb_entry *, /* client */
+ hdb_entry *, /* server */
+ const krb5_keyblock *, /* pk_replykey */
+ uint64_t, /* pac_attributes */
+ krb5_pac *);
+
+/*
+ * Verify the PAC KDC signatures by fetching the appropriate TGS key
+ * and calling krb5_pac_verify() with that key.
+ */
+
+typedef krb5_error_code
+(KRB5_CALLCONV *krb5plugin_kdc_pac_verify)(void *,
+ astgs_request_t,
+ krb5_const_principal, /* new ticket client */
+ hdb_entry *, /* delegation proxy */
+ hdb_entry *,/* client */
+ hdb_entry *,/* server */
+ hdb_entry *,/* krbtgt */
+ EncTicketPart *, /* ticket */
+ krb5_pac); /* pac */
+
+/*
+ * Update the KDC PAC buffers. This function may be used after verifying the PAC
+ * with a call to krb5plugin_kdc_pac_verify(), and it resembles the latter
+ * function in the parameters it takes. The 'pac' parameter always points to a
+ * non-NULL PAC.
+ */
+
+typedef krb5_error_code
+(KRB5_CALLCONV *krb5plugin_kdc_pac_update)(void *,
+ astgs_request_t,
+ krb5_const_principal, /* new ticket client */
+ hdb_entry *, /* delegation proxy */
+ krb5_const_pac, /* delegation proxy pac */
+ hdb_entry *,/* client */
+ hdb_entry *,/* server */
+ hdb_entry *,/* krbtgt */
+ krb5_pac *); /* pac */
+
+/*
+ * Authorize the client principal's access to the Authentication Service (AS).
+ * This function is called after any pre-authentication has completed.
+ */
+
+typedef krb5_error_code
+(KRB5_CALLCONV *krb5plugin_kdc_client_access)(void *, astgs_request_t);
+
+/*
+ * A referral policy plugin can either rewrite the server principal
+ * by resetting priv->server_princ, or it can disable referral
+ * processing entirely by returning an error.
+ *
+ * The error code from the previous server lookup is available as r->ret.
+ *
+ * If the function returns KRB5_PLUGIN_NO_HANDLE, the TGS will continue
+ * with its default referral handling.
+ *
+ * Note well: the plugin should free priv->server_princ is replacing.
+ */
+
+typedef krb5_error_code
+(KRB5_CALLCONV *krb5plugin_kdc_referral_policy)(void *, astgs_request_t);
+
+/*
+ * Update the AS or TGS reply immediately prior to encoding.
+ */
+
+typedef krb5_error_code
+(KRB5_CALLCONV *krb5plugin_kdc_finalize_reply)(void *, astgs_request_t);
+
+/*
+ * Audit an AS or TGS request. This function is called after encoding the
+ * reply (on success), or before encoding the error message. If a HDB audit
+ * function is also present, it is called after this one.
+ *
+ * The request should not be modified by the plugin.
+ */
+
+typedef krb5_error_code
+(KRB5_CALLCONV *krb5plugin_kdc_audit)(void *, astgs_request_t);
+
+/*
+ * Plugins should carefully check API contract notes for changes
+ * between plugin API versions.
+ */
+#define KRB5_PLUGIN_KDC_VERSION_11 11
+
+typedef struct krb5plugin_kdc_ftable {
+ HEIM_PLUGIN_FTABLE_COMMON_ELEMENTS(krb5_context);
+ krb5plugin_kdc_pac_generate pac_generate;
+ krb5plugin_kdc_pac_verify pac_verify;
+ krb5plugin_kdc_pac_update pac_update;
+ krb5plugin_kdc_client_access client_access;
+ krb5plugin_kdc_referral_policy referral_policy;
+ krb5plugin_kdc_finalize_reply finalize_reply;
+ krb5plugin_kdc_audit audit;
+} krb5plugin_kdc_ftable;
+
+#endif /* HEIMDAL_KDC_KDC_PLUGIN_H */
diff --git a/third_party/heimdal/kdc/kdc-replay.c b/third_party/heimdal/kdc/kdc-replay.c
new file mode 100644
index 0000000..29190f7
--- /dev/null
+++ b/third_party/heimdal/kdc/kdc-replay.c
@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) 2007 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "kdc_locl.h"
+
+static int version_flag;
+static int help_flag;
+
+struct getargs args[] = {
+ { "version", 0, arg_flag, &version_flag, NULL, NULL },
+ { "help", 'h', arg_flag, &help_flag, NULL, NULL }
+};
+
+static const int num_args = sizeof(args) / sizeof(args[0]);
+
+static void
+usage(int ret)
+{
+ arg_printusage (args, num_args, NULL, "kdc-request-log-file");
+ exit (ret);
+}
+
+int
+main(int argc, char **argv)
+{
+ krb5_error_code ret;
+ krb5_context context;
+ krb5_kdc_configuration *config;
+ krb5_storage *sp;
+ int fd, optidx = 0;
+
+ setprogname(argv[0]);
+
+ if(getarg(args, num_args, argc, argv, &optidx))
+ usage(1);
+
+ if(help_flag)
+ usage(0);
+
+ if(version_flag){
+ print_version(NULL);
+ exit(0);
+ }
+
+ ret = krb5_init_context(&context);
+ if (ret)
+ errx (1, "krb5_init_context failed to parse configuration file");
+
+ ret = krb5_kdc_get_config(context, &config);
+ if (ret)
+ krb5_err(context, 1, ret, "krb5_kdc_default_config");
+
+ kdc_openlog(context, "kdc-replay", config);
+
+ ret = krb5_kdc_set_dbinfo(context, config);
+ if (ret)
+ krb5_err(context, 1, ret, "krb5_kdc_set_dbinfo");
+
+#ifdef PKINIT
+ if (config->enable_pkinit) {
+ if (config->pkinit_kdc_identity == NULL)
+ krb5_errx(context, 1, "pkinit enabled but no identity");
+
+ if (config->pkinit_kdc_anchors == NULL)
+ krb5_errx(context, 1, "pkinit enabled but no X509 anchors");
+
+ krb5_kdc_pk_initialize(context, config,
+ config->pkinit_kdc_identity,
+ config->pkinit_kdc_anchors,
+ config->pkinit_kdc_cert_pool,
+ config->pkinit_kdc_revoke);
+
+ }
+#endif /* PKINIT */
+
+ if (argc != 2)
+ errx(1, "argc != 2");
+
+ printf("kdc replay\n");
+
+ fd = open(argv[1], O_RDONLY);
+ if (fd < 0)
+ err(1, "open: %s", argv[1]);
+
+ sp = krb5_storage_from_fd(fd);
+ if (sp == NULL)
+ krb5_errx(context, 1, "krb5_storage_from_fd");
+
+ while(1) {
+ struct sockaddr_storage sa;
+ krb5_socklen_t salen = sizeof(sa);
+ struct timeval tv;
+ krb5_address a;
+ krb5_data d, r;
+ uint32_t t, clty, tag;
+ char astr[80];
+
+ ret = krb5_ret_uint32(sp, &t);
+ if (ret == HEIM_ERR_EOF)
+ break;
+ else if (ret)
+ krb5_errx(context, 1, "krb5_ret_uint32(version)");
+ if (t != 1)
+ krb5_errx(context, 1, "version not 1");
+ ret = krb5_ret_uint32(sp, &t);
+ if (ret)
+ krb5_errx(context, 1, "krb5_ret_uint32(time)");
+ ret = krb5_ret_address(sp, &a);
+ if (ret)
+ krb5_errx(context, 1, "krb5_ret_address");
+ ret = krb5_ret_data(sp, &d);
+ if (ret)
+ krb5_errx(context, 1, "krb5_ret_data");
+ ret = krb5_ret_uint32(sp, &clty);
+ if (ret)
+ krb5_errx(context, 1, "krb5_ret_uint32(class|type)");
+ ret = krb5_ret_uint32(sp, &tag);
+ if (ret)
+ krb5_errx(context, 1, "krb5_ret_uint32(tag)");
+
+
+ ret = krb5_addr2sockaddr (context, &a, (struct sockaddr *)&sa,
+ &salen, 88);
+ if (ret == KRB5_PROG_ATYPE_NOSUPP)
+ goto out;
+ else if (ret)
+ krb5_err(context, 1, ret, "krb5_addr2sockaddr");
+
+ ret = krb5_print_address(&a, astr, sizeof(astr), NULL);
+ if (ret)
+ krb5_err(context, 1, ret, "krb5_print_address");
+
+ printf("processing request from %s, %lu bytes\n",
+ astr, (unsigned long)d.length);
+
+ r.length = 0;
+ r.data = NULL;
+
+ tv.tv_sec = t;
+ tv.tv_usec = 0;
+
+ krb5_kdc_update_time(&tv);
+ krb5_set_real_time(context, tv.tv_sec, 0);
+
+ ret = krb5_kdc_process_request(context, config, d.data, d.length,
+ &r, NULL, astr,
+ (struct sockaddr *)&sa, 0);
+ if (ret)
+ krb5_err(context, 1, ret, "krb5_kdc_process_request");
+
+ if (r.length) {
+ Der_class cl;
+ Der_type ty;
+ unsigned int tag2;
+ ret = der_get_tag (r.data, r.length,
+ &cl, &ty, &tag2, NULL);
+ if (ret)
+ krb5_err(context, 1, ret, "Could not decode replay data");
+ if (MAKE_TAG(cl, ty, 0) != clty)
+ krb5_errx(context, 1, "class|type mismatch: %d != %d",
+ (int)MAKE_TAG(cl, ty, 0), (int)clty);
+ if (tag != tag2)
+ krb5_errx(context, 1, "tag mismatch");
+
+ krb5_data_free(&r);
+ } else {
+ if (clty != 0xffffffff)
+ krb5_errx(context, 1, "clty not invalid");
+ if (tag != 0xffffffff)
+ krb5_errx(context, 1, "tag not invalid");
+ }
+
+ out:
+ krb5_data_free(&d);
+ krb5_free_address(context, &a);
+ }
+
+ krb5_storage_free(sp);
+ krb5_free_context(context);
+
+ printf("done\n");
+
+ return 0;
+}
diff --git a/third_party/heimdal/kdc/kdc-tester.c b/third_party/heimdal/kdc/kdc-tester.c
new file mode 100644
index 0000000..8f8073a
--- /dev/null
+++ b/third_party/heimdal/kdc/kdc-tester.c
@@ -0,0 +1,517 @@
+/*
+ * Copyright (c) 1997-2005 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "kdc_locl.h"
+#include "send_to_kdc_plugin.h"
+
+struct perf {
+ unsigned long as_req;
+ unsigned long tgs_req;
+ struct timeval start;
+ struct timeval stop;
+ struct perf *next;
+} *ptop;
+
+int detach_from_console = -1;
+int daemon_child = -1;
+int do_bonjour = -1;
+
+static krb5_kdc_configuration *kdc_config;
+static krb5_context kdc_context;
+
+static struct sockaddr_storage sa;
+static const char *astr = "0.0.0.0";
+
+static void eval_object(heim_object_t);
+
+
+/*
+ *
+ */
+
+static krb5_error_code
+plugin_init(krb5_context context, void **pctx)
+{
+ *pctx = NULL;
+ return 0;
+}
+
+static void
+plugin_fini(void *ctx)
+{
+}
+
+static krb5_error_code
+plugin_send_to_kdc(krb5_context context,
+ void *ctx,
+ krb5_krbhst_info *ho,
+ time_t timeout,
+ const krb5_data *in,
+ krb5_data *out)
+{
+ return KRB5_PLUGIN_NO_HANDLE;
+}
+
+static krb5_error_code
+plugin_send_to_realm(krb5_context context,
+ void *ctx,
+ krb5_const_realm realm,
+ time_t timeout,
+ const krb5_data *in,
+ krb5_data *out)
+{
+ int ret;
+
+ krb5_kdc_update_time(NULL);
+
+ ret = krb5_kdc_process_request(kdc_context, kdc_config,
+ in->data, in->length,
+ out, NULL, astr,
+ (struct sockaddr *)&sa, 0);
+ if (ret)
+ krb5_err(kdc_context, 1, ret, "krb5_kdc_process_request");
+
+ return 0;
+}
+
+static krb5plugin_send_to_kdc_ftable send_to_kdc = {
+ KRB5_PLUGIN_SEND_TO_KDC_VERSION_2,
+ plugin_init,
+ plugin_fini,
+ plugin_send_to_kdc,
+ plugin_send_to_realm
+};
+
+static void
+perf_start(struct perf *perf)
+{
+ memset(perf, 0, sizeof(*perf));
+
+ gettimeofday(&perf->start, NULL);
+ perf->next = ptop;
+ ptop = perf;
+}
+
+static void
+perf_stop(struct perf *perf)
+{
+ gettimeofday(&perf->stop, NULL);
+ ptop = perf->next;
+
+ if (ptop) {
+ ptop->as_req += perf->as_req;
+ ptop->tgs_req += perf->tgs_req;
+ }
+
+ timevalsub(&perf->stop, &perf->start);
+ printf("time: %lu.%06lu\n",
+ (unsigned long)perf->stop.tv_sec,
+ (unsigned long)perf->stop.tv_usec);
+
+#define USEC_PER_SEC 1000000
+
+ if (perf->as_req) {
+ double as_ps = 0.0;
+ as_ps = (perf->as_req * USEC_PER_SEC) / (double)((perf->stop.tv_sec * USEC_PER_SEC) + perf->stop.tv_usec);
+ printf("as-req/s %.2lf (total %lu requests)\n", as_ps, perf->as_req);
+ }
+
+ if (perf->tgs_req) {
+ double tgs_ps = 0.0;
+ tgs_ps = (perf->tgs_req * USEC_PER_SEC) / (double)((perf->stop.tv_sec * USEC_PER_SEC) + perf->stop.tv_usec);
+ printf("tgs-req/s %.2lf (total %lu requests)\n", tgs_ps, perf->tgs_req);
+ }
+}
+
+/*
+ *
+ */
+
+static void
+eval_repeat(heim_dict_t o)
+{
+ heim_object_t or = heim_dict_get_value(o, HSTR("value"));
+ heim_number_t n = heim_dict_get_value(o, HSTR("num"));
+ int i, num;
+ struct perf perf;
+
+ perf_start(&perf);
+
+ heim_assert(or != NULL, "value missing");
+ heim_assert(n != NULL, "num missing");
+
+ num = heim_number_get_int(n);
+ heim_assert(num >= 0, "num >= 0");
+
+ for (i = 0; i < num; i++)
+ eval_object(or);
+
+ perf_stop(&perf);
+}
+
+/*
+ *
+ */
+
+static krb5_error_code
+copy_keytab(krb5_context context, krb5_keytab from, krb5_keytab to)
+{
+ krb5_keytab_entry entry;
+ krb5_kt_cursor cursor;
+ krb5_error_code ret;
+
+ ret = krb5_kt_start_seq_get(context, from, &cursor);
+ if (ret)
+ return ret;
+ while ((ret = krb5_kt_next_entry(context, from, &entry, &cursor)) == 0){
+ krb5_kt_add_entry(context, to, &entry);
+ krb5_kt_free_entry(context, &entry);
+ }
+ (void) krb5_kt_end_seq_get(context, from, &cursor);
+ if (ret == KRB5_KT_END)
+ return 0;
+ return ret;
+}
+
+/*
+ *
+ */
+
+static void
+eval_kinit(heim_dict_t o)
+{
+ heim_string_t user, password, keytab, fast_armor_cc, pk_user_id, ccache;
+ krb5_get_init_creds_opt *opt;
+ krb5_init_creds_context ctx;
+ krb5_principal client;
+ krb5_keytab ktmem = NULL;
+ krb5_ccache fast_cc = NULL;
+ krb5_error_code ret;
+
+ if (ptop)
+ ptop->as_req++;
+
+ user = heim_dict_get_value(o, HSTR("client"));
+ if (user == NULL)
+ krb5_errx(kdc_context, 1, "no client");
+
+ password = heim_dict_get_value(o, HSTR("password"));
+ keytab = heim_dict_get_value(o, HSTR("keytab"));
+ pk_user_id = heim_dict_get_value(o, HSTR("pkinit-user-cert-id"));
+ if (password == NULL && keytab == NULL && pk_user_id == NULL)
+ krb5_errx(kdc_context, 1, "password, keytab, nor PKINIT user cert ID");
+
+ ccache = heim_dict_get_value(o, HSTR("ccache"));
+
+ ret = krb5_parse_name(kdc_context, heim_string_get_utf8(user), &client);
+ if (ret)
+ krb5_err(kdc_context, 1, ret, "krb5_unparse_name");
+
+ /* PKINIT parts */
+ ret = krb5_get_init_creds_opt_alloc (kdc_context, &opt);
+ if (ret)
+ krb5_err(kdc_context, 1, ret, "krb5_get_init_creds_opt_alloc");
+
+ if (pk_user_id) {
+ heim_bool_t rsaobj = heim_dict_get_value(o, HSTR("pkinit-use-rsa"));
+ int use_rsa = rsaobj ? heim_bool_val(rsaobj) : 0;
+
+ ret = krb5_get_init_creds_opt_set_pkinit(kdc_context, opt,
+ client,
+ heim_string_get_utf8(pk_user_id),
+ NULL, NULL, NULL,
+ use_rsa ? 2 : 0,
+ NULL, NULL, NULL);
+ if (ret)
+ krb5_err(kdc_context, 1, ret, "krb5_get_init_creds_opt_set_pkinit");
+ }
+
+ fast_armor_cc = heim_dict_get_value(o, HSTR("fast-armor-cc"));
+ if (fast_armor_cc) {
+
+ ret = krb5_cc_resolve(kdc_context, heim_string_get_utf8(fast_armor_cc), &fast_cc);
+ if (ret)
+ krb5_err(kdc_context, 1, ret, "krb5_cc_resolve");
+
+ ret = krb5_get_init_creds_opt_set_fast_ccache(kdc_context, opt, fast_cc);
+ if (ret)
+ krb5_err(kdc_context, 1, ret, "krb5_get_init_creds_set_fast_ccache");
+
+ ret = krb5_get_init_creds_opt_set_fast_flags(kdc_context, opt, KRB5_FAST_REQUIRED|KRB5_FAST_KDC_VERIFIED);
+ if (ret)
+ krb5_err(kdc_context, 1, ret, "krb5_get_init_creds_set_fast_ccache");
+
+ fast_cc = NULL;
+ }
+
+ ret = krb5_init_creds_init(kdc_context, client, NULL, NULL, 0, opt, &ctx);
+ if (ret)
+ krb5_err(kdc_context, 1, ret, "krb5_init_creds_init");
+
+ if (password) {
+ ret = krb5_init_creds_set_password(kdc_context, ctx,
+ heim_string_get_utf8(password));
+ if (ret)
+ krb5_err(kdc_context, 1, ret, "krb5_init_creds_set_password");
+ }
+ if (keytab) {
+ krb5_keytab kt = NULL;
+
+ ret = krb5_kt_resolve(kdc_context, heim_string_get_utf8(keytab), &kt);
+ if (ret)
+ krb5_err(kdc_context, 1, ret, "krb5_kt_resolve");
+
+ ret = krb5_kt_resolve(kdc_context, "MEMORY:keytab", &ktmem);
+ if (ret)
+ krb5_err(kdc_context, 1, ret, "krb5_kt_resolve(MEMORY)");
+
+ ret = copy_keytab(kdc_context, kt, ktmem);
+ if (ret)
+ krb5_err(kdc_context, 1, ret, "copy_keytab");
+
+ krb5_kt_close(kdc_context, kt);
+
+ ret = krb5_init_creds_set_keytab(kdc_context, ctx, ktmem);
+ if (ret)
+ krb5_err(kdc_context, 1, ret, "krb5_init_creds_set_keytab");
+ }
+
+ ret = krb5_init_creds_get(kdc_context, ctx);
+ if (ret)
+ krb5_err(kdc_context, 1, ret, "krb5_init_creds_get");
+
+ if (ccache) {
+ const char *name = heim_string_get_utf8(ccache);
+ krb5_creds cred;
+ krb5_ccache cc;
+
+ ret = krb5_init_creds_get_creds(kdc_context, ctx, &cred);
+ if (ret)
+ krb5_err(kdc_context, 1, ret, "krb5_init_creds_get_creds");
+
+ ret = krb5_cc_resolve(kdc_context, name, &cc);
+ if (ret)
+ krb5_err(kdc_context, 1, ret, "krb5_cc_resolve");
+
+ krb5_init_creds_store(kdc_context, ctx, cc);
+
+ ret = krb5_cc_close(kdc_context, cc);
+ if (ret)
+ krb5_err(kdc_context, 1, ret, "krb5_cc_close");
+
+ krb5_free_cred_contents(kdc_context, &cred);
+ }
+
+ krb5_init_creds_free(kdc_context, ctx);
+
+ if (ktmem)
+ krb5_kt_close(kdc_context, ktmem);
+ if (fast_cc)
+ krb5_cc_close(kdc_context, fast_cc);
+}
+
+/*
+ *
+ */
+
+static void
+eval_kgetcred(heim_dict_t o)
+{
+ heim_string_t server, ccache;
+ krb5_get_creds_opt opt;
+ heim_bool_t nostore;
+ krb5_error_code ret;
+ krb5_ccache cc = NULL;
+ krb5_principal s;
+ krb5_creds *out = NULL;
+
+ if (ptop)
+ ptop->tgs_req++;
+
+ server = heim_dict_get_value(o, HSTR("server"));
+ if (server == NULL)
+ krb5_errx(kdc_context, 1, "no server");
+
+ ccache = heim_dict_get_value(o, HSTR("ccache"));
+ if (ccache == NULL)
+ krb5_errx(kdc_context, 1, "no ccache");
+
+ nostore = heim_dict_get_value(o, HSTR("nostore"));
+ if (nostore == NULL)
+ nostore = heim_bool_create(1);
+
+ ret = krb5_cc_resolve(kdc_context, heim_string_get_utf8(ccache), &cc);
+ if (ret)
+ krb5_err(kdc_context, 1, ret, "krb5_cc_resolve");
+
+ ret = krb5_parse_name(kdc_context, heim_string_get_utf8(server), &s);
+ if (ret)
+ krb5_err(kdc_context, 1, ret, "krb5_parse_name");
+
+ ret = krb5_get_creds_opt_alloc(kdc_context, &opt);
+ if (ret)
+ krb5_err(kdc_context, 1, ret, "krb5_get_creds_opt_alloc");
+
+ if (heim_bool_val(nostore))
+ krb5_get_creds_opt_add_options(kdc_context, opt, KRB5_GC_NO_STORE);
+
+ ret = krb5_get_creds(kdc_context, opt, cc, s, &out);
+ if (ret)
+ krb5_err(kdc_context, 1, ret, "krb5_get_creds");
+
+ krb5_free_creds(kdc_context, out);
+ krb5_free_principal(kdc_context, s);
+ krb5_get_creds_opt_free(kdc_context, opt);
+ krb5_cc_close(kdc_context, cc);
+}
+
+
+/*
+ *
+ */
+
+static void
+eval_kdestroy(heim_dict_t o)
+{
+ heim_string_t ccache = heim_dict_get_value(o, HSTR("ccache"));;
+ krb5_error_code ret;
+ const char *name;
+ krb5_ccache cc;
+
+ heim_assert(ccache != NULL, "ccache_missing");
+
+ name = heim_string_get_utf8(ccache);
+
+ ret = krb5_cc_resolve(kdc_context, name, &cc);
+ if (ret)
+ krb5_err(kdc_context, 1, ret, "krb5_cc_resolve");
+
+ krb5_cc_destroy(kdc_context, cc);
+}
+
+
+/*
+ *
+ */
+
+static void
+eval_array_element(heim_object_t o, void *ptr, int *stop)
+{
+ eval_object(o);
+}
+
+static void
+eval_object(heim_object_t o)
+{
+ heim_tid_t t = heim_get_tid(o);
+
+ if (t == heim_array_get_type_id()) {
+ heim_array_iterate_f(o, NULL, eval_array_element);
+ } else if (t == heim_dict_get_type_id()) {
+ const char *op = heim_dict_get_value(o, HSTR("op"));
+
+ heim_assert(op != NULL, "op missing");
+
+ if (strcmp(op, "repeat") == 0) {
+ eval_repeat(o);
+ } else if (strcmp(op, "kinit") == 0) {
+ eval_kinit(o);
+ } else if (strcmp(op, "kgetcred") == 0) {
+ eval_kgetcred(o);
+ } else if (strcmp(op, "kdestroy") == 0) {
+ eval_kdestroy(o);
+ } else {
+ errx(1, "unsupported ops %s", op);
+ }
+
+ } else
+ errx(1, "unsupported");
+}
+
+
+int
+main(int argc, char **argv)
+{
+ krb5_error_code ret;
+ int optidx = 0;
+
+ setprogname(argv[0]);
+
+ ret = krb5_init_context(&kdc_context);
+ if (ret == KRB5_CONFIG_BADFORMAT)
+ errx (1, "krb5_init_context failed to parse configuration file");
+ else if (ret)
+ errx (1, "krb5_init_context failed: %d", ret);
+
+ ret = krb5_kt_register(kdc_context, &hdb_get_kt_ops);
+ if (ret)
+ errx (1, "krb5_kt_register(HDB) failed: %d", ret);
+
+ kdc_config = configure(kdc_context, argc, argv, &optidx);
+
+ argc -= optidx;
+ argv += optidx;
+
+ if (argc == 0)
+ errx(1, "missing operations");
+
+ krb5_plugin_register(kdc_context, PLUGIN_TYPE_DATA,
+ KRB5_PLUGIN_SEND_TO_KDC, &send_to_kdc);
+
+ {
+ void *buf;
+ size_t size;
+ heim_object_t o;
+
+ if (rk_undumpdata(argv[0], &buf, &size))
+ errx(1, "undumpdata: %s", argv[0]);
+
+ o = heim_json_create_with_bytes(buf, size, 10, 0, NULL);
+ free(buf);
+ if (o == NULL)
+ errx(1, "heim_json");
+
+ /*
+ * do the work here
+ */
+
+ eval_object(o);
+
+ heim_release(o);
+ }
+
+ krb5_free_context(kdc_context);
+ return 0;
+}
diff --git a/third_party/heimdal/kdc/kdc-version.rc b/third_party/heimdal/kdc/kdc-version.rc
new file mode 100644
index 0000000..662aff4
--- /dev/null
+++ b/third_party/heimdal/kdc/kdc-version.rc
@@ -0,0 +1,36 @@
+/***********************************************************************
+ * Copyright (c) 2010, Secure Endpoints Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ **********************************************************************/
+
+#define RC_FILE_TYPE VFT_APP
+#define RC_FILE_DESC_0409 "Heimdal Kerberos v5 Server"
+#define RC_FILE_ORIG_0409 "kdc.exe"
+
+#include "../windows/version.rc"
diff --git a/third_party/heimdal/kdc/kdc.8 b/third_party/heimdal/kdc/kdc.8
new file mode 100644
index 0000000..150a3f1
--- /dev/null
+++ b/third_party/heimdal/kdc/kdc.8
@@ -0,0 +1,217 @@
+.\" Copyright (c) 2003 - 2004 Kungliga Tekniska Högskolan
+.\" (Royal Institute of Technology, Stockholm, Sweden).
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\"
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\"
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" 3. Neither the name of the Institute nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $Id$
+.\"
+.Dd August 24, 2006
+.Dt KDC 8
+.Os HEIMDAL
+.Sh NAME
+.Nm kdc
+.Nd Kerberos 5 server
+.Sh SYNOPSIS
+.Nm
+.Bk -words
+.Oo Fl c Ar file \*(Ba Xo
+.Fl Fl config-file= Ns Ar file
+.Xc
+.Oc
+.Op Fl p | Fl Fl no-require-preauth
+.Op Fl Fl max-request= Ns Ar size
+.Op Fl H | Fl Fl enable-http
+.Oo Fl P Ar portspec \*(Ba Xo
+.Fl Fl ports= Ns Ar portspec
+.Xc
+.Oc
+.Op Fl Fl detach
+.Op Fl Fl disable-des
+.Op Fl Fl addresses= Ns Ar list of addresses
+.Ek
+.Sh DESCRIPTION
+.Nm
+serves requests for tickets.
+When it starts, it first checks the flags passed, any options that are
+not specified with a command line flag are taken from a config file,
+or from a default compiled-in value.
+.Pp
+Options supported:
+.Bl -tag -width Ds
+.It Fl c Ar file , Fl Fl config-file= Ns Ar file
+Specifies the location of the config file, the default is
+.Pa /var/heimdal/kdc.conf .
+This is the only value that can't be specified in the config file.
+.It Fl p , Fl Fl no-require-preauth
+Turn off the requirement for pre-autentication in the initial AS-REQ
+for all principals.
+The use of pre-authentication makes it more difficult to do offline
+password attacks.
+You might want to turn it off if you have clients
+that don't support pre-authentication.
+Since the version 4 protocol doesn't support any pre-authentication,
+serving version 4 clients is just about the same as not requiring
+pre-athentication.
+The default is to require pre-authentication.
+Adding the require-preauth per principal is a more flexible way of
+handling this.
+.It Fl Fl max-request= Ns Ar size
+Gives an upper limit on the size of the requests that the kdc is
+willing to handle.
+.It Fl H , Fl Fl enable-http
+Makes the kdc listen on port 80 and handle requests encapsulated in HTTP.
+.It Fl P Ar portspec , Fl Fl ports= Ns Ar portspec
+Specifies the set of ports the KDC should listen on.
+It is given as a
+white-space separated list of services or port numbers.
+.It Fl Fl addresses= Ns Ar list of addresses
+The list of addresses to listen for requests on.
+By default, the kdc will listen on all the locally configured
+addresses.
+If only a subset is desired, or the automatic detection fails, this
+option might be used.
+.It Fl Fl detach
+detach from pty and run as a daemon.
+.It Fl Fl disable-des
+disable all des encryption types, makes the kdc not use them.
+.El
+.Pp
+All activities are logged to one or more destinations, see
+.Xr krb5.conf 5 ,
+and
+.Xr krb5_openlog 3 .
+The entity used for logging is
+.Nm kdc .
+.Sh CONFIGURATION FILE
+The configuration file has the same syntax as
+.Xr krb5.conf 5 ,
+but will be read before
+.Pa /etc/krb5.conf ,
+so it may override settings found there.
+Options specific to the KDC only are found in the
+.Dq [kdc]
+section.
+All the command-line options can preferably be added in the
+configuration file.
+The only difference is the pre-authentication flag, which has to be
+specified as:
+.Pp
+.Dl require-preauth = no
+.Pp
+(in fact you can specify the option as
+.Fl Fl require-preauth=no ) .
+.Pp
+And there are some configuration options which do not have
+command-line equivalents:
+.Bl -tag -width "xxx" -offset indent
+.It Li enable-digest = Va boolean
+turn on support for digest processing in the KDC.
+The default is FALSE.
+.It Li check-ticket-addresses = Va boolean
+Check the addresses in the ticket when processing TGS requests.
+The default is TRUE.
+.It Li allow-null-ticket-addresses = Va boolean
+Permit tickets with no addresses.
+This option is only relevant when check-ticket-addresses is TRUE.
+.It Li allow-anonymous = Va boolean
+Permit anonymous tickets with no addresses.
+.It Li historical_anon_realm = Va boolean
+Enables pre-7.0 non-RFC-comformant KDC behavior.
+With this option set to
+.Li true
+the client realm in anonymous pkinit AS replies will be the requested realm,
+rather than the RFC-conformant
+.Li WELLKNOWN:ANONYMOUS
+realm.
+This can have a security impact on servers that expect to grant access to
+anonymous-but-authenticated to the KDC users of the realm in question:
+they would also grant access to unauthenticated anonymous users.
+As such, it is not recommend to set this option to
+.Li true.
+.It Li max-kdc-datagram-reply-length = Va number
+Maximum packet size the UDP rely that the KDC will transmit, instead
+the KDC sends back a reply telling the client to use TCP instead.
+.It Li transited-policy = Li always-check \*(Ba \
+Li allow-per-principal | Li always-honour-request
+This controls how KDC requests with the
+.Li disable-transited-check
+flag are handled. It can be one of:
+.Bl -tag -width "xxx" -offset indent
+.It Li always-check
+Always check transited encoding, this is the default.
+.It Li allow-per-principal
+Currently this is identical to
+.Li always-check .
+In a future release, it will be possible to mark a principal as able
+to handle unchecked requests.
+.It Li always-honour-request
+Always do what the client asked.
+In a future release, it will be possible to force a check per
+principal.
+.El
+.It encode_as_rep_as_tgs_rep = Va boolean
+Encode AS-Rep as TGS-Rep to be bug-compatible with old DCE code.
+The Heimdal clients allow both.
+.It kdc_warn_pwexpire = Va time
+How long before password/principal expiration the KDC should start
+sending out warning messages.
+.El
+.Pp
+The configuration file is only read when the
+.Nm
+is started.
+If changes made to the configuration file are to take effect, the
+.Nm
+needs to be restarted.
+.Pp
+An example of a config file:
+.Bd -literal -offset indent
+[kdc]
+ require-preauth = no
+.Ed
+.Sh BUGS
+If the machine running the KDC has new addresses added to it, the KDC
+will have to be restarted to listen to them.
+The reason it doesn't just listen to wildcarded (like INADDR_ANY)
+addresses, is that the replies has to come from the same address they
+were sent to, and most OS:es doesn't pass this information to the
+application.
+If your normal mode of operation require that you add and remove
+addresses, the best option is probably to listen to a wildcarded TCP
+socket, and make sure your clients use TCP to connect.
+For instance, this will listen to IPv4 TCP port 88 only:
+.Bd -literal -offset indent
+kdc --addresses=0.0.0.0 --ports="88/tcp"
+.Ed
+.Pp
+There should be a way to specify protocol, port, and address triplets,
+not just addresses and protocol, port tuples.
+.Sh SEE ALSO
+.Xr kinit 1 ,
+.Xr krb5.conf 5
diff --git a/third_party/heimdal/kdc/kdc.h b/third_party/heimdal/kdc/kdc.h
new file mode 100644
index 0000000..057d29a
--- /dev/null
+++ b/third_party/heimdal/kdc/kdc.h
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 1997-2022 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ *
+ * Copyright (c) 2005 Andrew Bartlett <abartlet@samba.org>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * $Id$
+ */
+
+#ifndef __KDC_H__
+#define __KDC_H__
+
+#include <hdb.h>
+#include <krb5.h>
+#include <kx509_asn1.h>
+#include <gssapi/gssapi.h>
+
+enum krb5_kdc_trpolicy {
+ TRPOLICY_ALWAYS_CHECK,
+ TRPOLICY_ALLOW_PER_PRINCIPAL,
+ TRPOLICY_ALWAYS_HONOUR_REQUEST
+};
+
+struct krb5_kdc_configuration;
+typedef struct krb5_kdc_configuration krb5_kdc_configuration;
+
+/*
+ * Access to request fields by plugins and other out-of-tree
+ * consumers should be via the functions in kdc-accessors.h.
+ */
+
+struct kdc_request_desc;
+typedef struct kdc_request_desc *kdc_request_t;
+
+struct astgs_request_desc;
+typedef struct astgs_request_desc *astgs_request_t;
+
+struct kx509_req_context_desc;
+typedef struct kx509_req_context_desc *kx509_req_context;
+
+struct krb5_kdc_service {
+ unsigned int flags;
+#define KS_KRB5 1
+#define KS_NO_LENGTH 2
+ const char *name;
+ krb5_error_code (*process)(kdc_request_t *, int *claim);
+};
+
+/*
+ * The following fields are guaranteed stable within a major
+ * release of Heimdal and can be manipulated by applications
+ * that manage KDC requests themselves using libkdc.
+ *
+ * Applications can make custom KDC configuration available
+ * to libkdc by using krb5_set_config().
+ */
+
+#define KRB5_KDC_CONFIGURATION_COMMON_ELEMENTS \
+ krb5_log_facility *logf; \
+ struct HDB **db; \
+ size_t num_db; \
+ const char *app; \
+ \
+ /*
+ * If non-null, contains static dummy data to include in
+ * place of the FAST cookie when it is disabled.
+ */ \
+ krb5_data dummy_fast_cookie; \
+ \
+ /* \
+ * Windows 2019 (and earlier versions) always sends the salt\
+ * and Samba has testsuites that check this behaviour, so a \
+ * Samba AD DC will set this flag to match the AS-REP packet\
+ * exactly. \
+ */ \
+ unsigned int force_include_pa_etype_salt : 1; \
+ \
+ unsigned int tgt_use_strongest_session_key : 1; \
+ unsigned int preauth_use_strongest_session_key : 1; \
+ unsigned int svc_use_strongest_session_key : 1; \
+ unsigned int use_strongest_server_key : 1; \
+ \
+ unsigned int require_pac : 1; \
+ unsigned int enable_fast : 1; \
+ unsigned int enable_fast_cookie : 1; \
+ unsigned int enable_armored_pa_enc_timestamp : 1
+
+#ifndef __KDC_LOCL_H__
+struct krb5_kdc_configuration {
+ KRB5_KDC_CONFIGURATION_COMMON_ELEMENTS;
+};
+#endif
+
+typedef void *kdc_object_t;
+typedef struct kdc_array_data *kdc_array_t;
+typedef struct kdc_dict_data *kdc_dict_t;
+typedef struct kdc_string_data *kdc_string_t;
+typedef struct kdc_data_data *kdc_data_t;
+typedef struct kdc_number_data *kdc_number_t;
+
+typedef void (KRB5_CALLCONV *kdc_array_iterator_t)(kdc_object_t, void *, int *);
+
+typedef void (KRB5_CALLCONV *kdc_type_dealloc)(kdc_object_t);
+
+#include <kdc-protos.h>
+
+#endif /* __KDC_H__ */
diff --git a/third_party/heimdal/kdc/kdc_locl.h b/third_party/heimdal/kdc/kdc_locl.h
new file mode 100644
index 0000000..f2160b7
--- /dev/null
+++ b/third_party/heimdal/kdc/kdc_locl.h
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 1997-2005 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * $Id$
+ */
+
+#ifndef __KDC_LOCL_H__
+#define __KDC_LOCL_H__
+
+#include "headers.h"
+
+typedef struct pk_client_params pk_client_params;
+typedef struct gss_client_params gss_client_params;
+
+#include <kdc-private.h>
+
+#define FAST_EXPIRATION_TIME (3 * 60)
+
+/* KFE == KDC_FIND_ETYPE */
+#define KFE_IS_TGS 0x1
+#define KFE_IS_PREAUTH 0x2
+#define KFE_USE_CLIENT 0x4
+
+#define heim_pcontext krb5_context
+#define heim_pconfig krb5_kdc_configuration *
+#include <heimbase-svc.h>
+
+#define KDC_AUDIT_EATWHITE HEIM_SVC_AUDIT_EATWHITE
+#define KDC_AUDIT_VIS HEIM_SVC_AUDIT_VIS
+#define KDC_AUDIT_VISLAST HEIM_SVC_AUDIT_VISLAST
+
+struct kdc_request_desc {
+ HEIM_SVC_REQUEST_DESC_COMMON_ELEMENTS;
+};
+
+struct kdc_patypes;
+
+struct krb5_kdc_configuration {
+ KRB5_KDC_CONFIGURATION_COMMON_ELEMENTS;
+
+ int num_kdc_processes;
+
+ size_t max_datagram_reply_length;
+
+ time_t kdc_warn_pwexpire; /* time before expiration to print a warning */
+
+ unsigned int require_preauth : 1; /* require preauth for all principals */
+ unsigned int encode_as_rep_as_tgs_rep : 1; /* bug compatibility */
+
+ unsigned int check_ticket_addresses : 1;
+ unsigned int warn_ticket_addresses : 1;
+ unsigned int allow_null_ticket_addresses : 1;
+ unsigned int allow_anonymous : 1;
+ unsigned int historical_anon_realm : 1;
+ unsigned int strict_nametypes : 1;
+ enum krb5_kdc_trpolicy trpolicy;
+
+ unsigned int disable_pac : 1;
+ unsigned int enable_unarmored_pa_enc_timestamp : 1;
+
+ unsigned int enable_pkinit : 1;
+ unsigned int require_pkinit_freshness : 1;
+ unsigned int pkinit_princ_in_cert : 1;
+ const char *pkinit_kdc_identity;
+ const char *pkinit_kdc_anchors;
+ const char *pkinit_kdc_friendly_name;
+ const char *pkinit_kdc_ocsp_file;
+ char **pkinit_kdc_cert_pool;
+ char **pkinit_kdc_revoke;
+ int pkinit_dh_min_bits;
+ unsigned int pkinit_require_binding : 1;
+ unsigned int pkinit_allow_proxy_certs : 1;
+ unsigned int synthetic_clients : 1;
+ unsigned int pkinit_max_life_from_cert_extension : 1;
+ krb5_timestamp pkinit_max_life_from_cert;
+ krb5_timestamp pkinit_max_life_bound;
+ krb5_timestamp synthetic_clients_max_life;
+ krb5_timestamp synthetic_clients_max_renew;
+
+ int digests_allowed;
+ unsigned int enable_digest : 1;
+
+ unsigned int enable_kx509 : 1;
+
+ unsigned int enable_gss_preauth : 1;
+ unsigned int enable_gss_auth_data : 1;
+ gss_OID_set gss_mechanisms_allowed;
+ gss_OID_set gss_cross_realm_mechanisms_allowed;
+
+};
+
+struct astgs_request_desc {
+ HEIM_SVC_REQUEST_DESC_COMMON_ELEMENTS;
+
+ /* AS-REQ or TGS-REQ */
+ KDC_REQ req;
+
+ /* AS-REP or TGS-REP */
+ KDC_REP rep;
+ EncTicketPart et;
+ EncKDCRepPart ek;
+
+ /* client principal (AS) or TGT/S4U principal (TGS) */
+ krb5_principal client_princ;
+ hdb_entry *client;
+ HDB *clientdb;
+ krb5_principal canon_client_princ;
+
+ /* server principal */
+ krb5_principal server_princ;
+ HDB *serverdb;
+ hdb_entry *server;
+
+ /* presented ticket in TGS-REQ (unused by AS) */
+ krb5_principal krbtgt_princ;
+ hdb_entry *krbtgt;
+ HDB *krbtgtdb;
+ krb5_ticket *ticket;
+
+ krb5_keyblock reply_key;
+
+ krb5_pac pac;
+ uint64_t pac_attributes;
+
+ /* Only AS */
+ const struct kdc_patypes *pa_used;
+
+ /* PA methods can affect both the reply key and the session key (pkinit) */
+ krb5_enctype sessionetype;
+ krb5_keyblock session_key;
+
+ krb5_timestamp pa_endtime;
+ krb5_timestamp pa_max_life;
+
+ krb5_keyblock strengthen_key;
+ const Key *ticket_key;
+
+ /* only valid for tgs-req */
+ unsigned int rk_is_subkey : 1;
+ unsigned int fast_asserted : 1;
+ unsigned int explicit_armor_present : 1;
+ krb5_keyblock enc_ad_key;
+
+ krb5_crypto armor_crypto;
+ hdb_entry *armor_server;
+ HDB *armor_serverdb;
+ krb5_ticket *armor_ticket;
+ Key *armor_key;
+
+ hdb_entry *armor_client;
+ HDB *armor_clientdb;
+ krb5_pac armor_pac;
+
+ KDCFastState fast;
+};
+
+typedef struct kx509_req_context_desc {
+ HEIM_SVC_REQUEST_DESC_COMMON_ELEMENTS;
+
+ struct Kx509Request req;
+ Kx509CSRPlus csr_plus;
+ krb5_auth_context ac;
+ const char *realm; /* XXX Confusion: is this crealm or srealm? */
+ krb5_keyblock *key;
+ hx509_request csr;
+ krb5_times ticket_times;
+ unsigned int send_chain:1; /* Client expects a full chain */
+ unsigned int have_csr:1; /* Client sent a CSR */
+} *kx509_req_context;
+
+#undef heim_pconfig
+#undef heim_pcontext
+
+extern sig_atomic_t exit_flag;
+extern size_t max_request_udp;
+extern size_t max_request_tcp;
+extern const char *request_log;
+extern const char *port_str;
+extern krb5_addresses explicit_addresses;
+
+extern int enable_http;
+
+extern int detach_from_console;
+extern int daemon_child;
+extern int do_bonjour;
+
+extern int testing_flag;
+
+extern const struct units _kdc_digestunits[];
+
+#define KDC_LOG_FILE "kdc.log"
+
+extern struct timeval _kdc_now;
+#define kdc_time (_kdc_now.tv_sec)
+
+extern char *runas_string;
+extern char *chroot_string;
+
+void
+start_kdc(krb5_context context, krb5_kdc_configuration *config, const char *argv0);
+
+krb5_kdc_configuration *
+configure(krb5_context context, int argc, char **argv, int *optidx);
+
+#ifdef __APPLE__
+void bonjour_announce(krb5_context, krb5_kdc_configuration *);
+#endif
+
+/* no-copy setters */
+
+#undef _KDC_REQUEST_GET_ACCESSOR
+#undef _KDC_REQUEST_SET_ACCESSOR
+
+#undef _KDC_REQUEST_GET_ACCESSOR_PTR
+#undef _KDC_REQUEST_SET_ACCESSOR_PTR
+#define _KDC_REQUEST_SET_ACCESSOR_PTR(R, T, t, f) \
+ void \
+ _kdc_request_set_ ## f ## _nocopy(R r, T *v);
+
+#undef _KDC_REQUEST_GET_ACCESSOR_STRUCT
+#undef _KDC_REQUEST_SET_ACCESSOR_STRUCT
+#define _KDC_REQUEST_SET_ACCESSOR_STRUCT(R, T, t, f) \
+ void \
+ _kdc_request_set_ ## f ## _nocopy(R r, T *v);
+
+#undef HEIMDAL_KDC_KDC_ACCESSORS_H
+#include "kdc-accessors.h"
+
+#endif /* __KDC_LOCL_H__ */
diff --git a/third_party/heimdal/kdc/kerberos5.c b/third_party/heimdal/kdc/kerberos5.c
new file mode 100644
index 0000000..5991711
--- /dev/null
+++ b/third_party/heimdal/kdc/kerberos5.c
@@ -0,0 +1,3247 @@
+/*
+ * Copyright (c) 1997-2007 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "kdc_locl.h"
+
+#ifdef TIME_T_SIGNED
+#if SIZEOF_TIME_T == 4
+#define MAX_TIME ((time_t)INT32_MAX)
+#elif SIZEOF_TIME_T == 8
+#define MAX_TIME ((time_t)INT64_MAX)
+#else
+#error "Unexpected sizeof(time_t)"
+#endif
+#else
+
+#if SIZEOF_TIME_T == 4
+#define MAX_TIME ((time_t)UINT32_MAX)
+#else
+#define MAX_TIME ((time_t)UINT64_MAX)
+#endif
+#endif
+
+#undef __attribute__
+#define __attribute__(X)
+
+void
+_kdc_fix_time(time_t **t)
+{
+ if(*t == NULL){
+ ALLOC(*t);
+ **t = MAX_TIME;
+ }
+ if(**t == 0) **t = MAX_TIME; /* fix for old clients */
+}
+
+static int
+realloc_method_data(METHOD_DATA *md)
+{
+ PA_DATA *pa;
+ pa = realloc(md->val, (md->len + 1) * sizeof(*md->val));
+ if(pa == NULL)
+ return ENOMEM;
+ md->val = pa;
+ md->len++;
+ return 0;
+}
+
+static krb5_error_code
+get_pa_etype_info2(krb5_context context,
+ krb5_kdc_configuration *config,
+ METHOD_DATA *md, Key *ckey,
+ krb5_boolean include_salt);
+
+static krb5_error_code
+set_salt_padata(krb5_context context,
+ krb5_kdc_configuration *config,
+ METHOD_DATA *md, Key *key)
+{
+ if (!key->salt)
+ return 0;
+
+ return get_pa_etype_info2(context, config, md, key, TRUE);
+}
+
+const PA_DATA*
+_kdc_find_padata(const KDC_REQ *req, int *start, int type)
+{
+ if (req->padata == NULL)
+ return NULL;
+
+ while((size_t)*start < req->padata->len){
+ (*start)++;
+ if(req->padata->val[*start - 1].padata_type == (unsigned)type)
+ return &req->padata->val[*start - 1];
+ }
+ return NULL;
+}
+
+/*
+ * This is a hack to allow predefined weak services, like afs to
+ * still use weak types
+ */
+
+krb5_boolean
+_kdc_is_weak_exception(krb5_principal principal, krb5_enctype etype)
+{
+ if (principal->name.name_string.len > 0 &&
+ strcmp(principal->name.name_string.val[0], "afs") == 0 &&
+ (etype == ETYPE_DES_CBC_CRC
+ || etype == ETYPE_DES_CBC_MD4
+ || etype == ETYPE_DES_CBC_MD5))
+ return TRUE;
+ return FALSE;
+}
+
+
+/*
+ * Detect if `key' is the using the the precomputed `default_salt'.
+ */
+
+static krb5_boolean
+is_default_salt_p(const krb5_salt *default_salt, const Key *key)
+{
+ if (key->salt == NULL)
+ return TRUE;
+ if (default_salt->salttype != key->salt->type)
+ return FALSE;
+ if (krb5_data_cmp(&default_salt->saltvalue, &key->salt->salt) != 0)
+ return FALSE;
+ return TRUE;
+}
+
+/*
+ * Detect if `key' is the using the the precomputed `default_salt'
+ * (for des-cbc-crc) or any salt otherwise.
+ *
+ * This is for avoiding Kerberos v4 (yes really) keys in AS-REQ as
+ * that salt is strange, and a buggy client will try to use the
+ * principal as the salt and not the returned value.
+ */
+
+static krb5_boolean
+is_good_salt_p(const krb5_salt *default_salt, const Key *key)
+{
+ if (key->key.keytype == KRB5_ENCTYPE_DES_CBC_CRC)
+ return is_default_salt_p(default_salt, key);
+
+ return TRUE;
+}
+
+krb5_boolean
+_kdc_is_anon_request(const KDC_REQ *req)
+{
+ const KDC_REQ_BODY *b = &req->req_body;
+
+ /*
+ * Versions of Heimdal from 0.9rc1 through 1.50 use bit 14 instead
+ * of 16 for request_anonymous, as indicated in the anonymous draft
+ * prior to version 11. Bit 14 is assigned to S4U2Proxy, but S4U2Proxy
+ * requests are only sent to the TGS and, in any case, would have an
+ * additional ticket present.
+ */
+ return b->kdc_options.request_anonymous ||
+ (b->kdc_options.cname_in_addl_tkt && !b->additional_tickets);
+}
+
+/*
+ * return the first appropriate key of `princ' in `ret_key'. Look for
+ * all the etypes in (`etypes', `len'), stopping as soon as we find
+ * one, but preferring one that has default salt.
+ *
+ * XXX This function does way way too much. Split it up!
+ *
+ * XXX `etypes' and `len' are always `b->etype.val' and `b->etype.len' -- the
+ * etype list from the KDC-REQ-BODY, which is available here as
+ * `r->req->req_body', so we could just stop having it passed in.
+ *
+ * XXX Picking an enctype(s) for PA-ETYPE-INFO* is rather different than
+ * picking an enctype for a ticket's session key. The former is what we do
+ * here when `(flags & KFE_IS_PREAUTH)', the latter otherwise.
+ */
+
+krb5_error_code
+_kdc_find_etype(astgs_request_t r, uint32_t flags,
+ krb5_enctype *etypes, unsigned len,
+ krb5_enctype *ret_enctype, Key **ret_key,
+ krb5_boolean *ret_default_salt)
+{
+ krb5_boolean use_strongest_session_key;
+ krb5_boolean is_preauth = flags & KFE_IS_PREAUTH;
+ krb5_boolean is_tgs = flags & KFE_IS_TGS;
+ hdb_entry *princ;
+ krb5_principal request_princ;
+ krb5_error_code ret;
+ krb5_salt def_salt;
+ krb5_enctype enctype = ETYPE_NULL;
+ const krb5_enctype *p;
+ Key *key = NULL;
+ size_t i, k, m;
+
+ if (is_preauth && (flags & KFE_USE_CLIENT) &&
+ r->client->flags.synthetic)
+ return KRB5KDC_ERR_ETYPE_NOSUPP;
+
+ if ((flags & KFE_USE_CLIENT) && !r->client->flags.synthetic) {
+ princ = r->client;
+ request_princ = r->client_princ;
+ } else {
+ princ = r->server;
+ request_princ = r->server->principal;
+ }
+
+ use_strongest_session_key =
+ is_preauth ? r->config->preauth_use_strongest_session_key
+ : (is_tgs ? r->config->tgt_use_strongest_session_key :
+ r->config->svc_use_strongest_session_key);
+
+ /* We'll want to avoid keys with v4 salted keys in the pre-auth case... */
+ ret = krb5_get_pw_salt(r->context, request_princ, &def_salt);
+ if (ret)
+ return ret;
+
+ ret = KRB5KDC_ERR_ETYPE_NOSUPP;
+
+ /*
+ * Pick an enctype that is in the intersection of:
+ *
+ * - permitted_enctypes (local policy)
+ * - requested enctypes (KDC-REQ-BODY's etype list)
+ * - the client's long-term keys' enctypes
+ * OR
+ * the server's configured etype list
+ *
+ * There are two sub-cases:
+ *
+ * - use local enctype preference (local policy)
+ * - use the client's preference list
+ */
+
+ if (use_strongest_session_key) {
+ /*
+ * Pick the strongest key that the KDC, target service, and
+ * client all support, using the local cryptosystem enctype
+ * list in strongest-to-weakest order to drive the search.
+ *
+ * This is not what RFC4120 says to do, but it encourages
+ * adoption of stronger enctypes. This doesn't play well with
+ * clients that have multiple Kerberos client implementations
+ * with different supported enctype lists sharing the same ccache.
+ */
+
+ /* drive the search with local supported enctypes list */
+ p = krb5_kerberos_enctypes(r->context);
+ for (i = 0;
+ p[i] != ETYPE_NULL && enctype == ETYPE_NULL;
+ i++) {
+ if (krb5_enctype_valid(r->context, p[i]) != 0 &&
+ !_kdc_is_weak_exception(princ->principal, p[i]))
+ continue;
+
+ /* check that the client supports it too */
+ for (k = 0; k < len && enctype == ETYPE_NULL; k++) {
+
+ if (p[i] != etypes[k])
+ continue;
+
+ if (!is_preauth && (flags & KFE_USE_CLIENT)) {
+ /*
+ * It suffices that the client says it supports this
+ * enctype in its KDC-REQ-BODY's etype list, which is what
+ * `etypes' is here.
+ */
+ enctype = p[i];
+ ret = 0;
+ break;
+ }
+
+ /* check target princ support */
+ key = NULL;
+ if (!is_preauth && !(flags & KFE_USE_CLIENT) && princ->etypes) {
+ /*
+ * Use the etypes list from the server's HDB entry instead
+ * of deriving it from its long-term keys. This allows an
+ * entry to have just one long-term key but record support
+ * for multiple enctypes.
+ */
+ for (m = 0; m < princ->etypes->len; m++) {
+ if (p[i] == princ->etypes->val[m]) {
+ enctype = p[i];
+ ret = 0;
+ break;
+ }
+ }
+ } else {
+ /*
+ * Use the entry's long-term keys as the source of its
+ * supported enctypes, either because we're making
+ * PA-ETYPE-INFO* or because we're selecting a session key
+ * enctype.
+ */
+ while (hdb_next_enctype2key(r->context, princ, NULL,
+ p[i], &key) == 0) {
+ if (key->key.keyvalue.length == 0) {
+ ret = KRB5KDC_ERR_NULL_KEY;
+ continue;
+ }
+ enctype = p[i];
+ ret = 0;
+ if (is_preauth && ret_key != NULL &&
+ !is_good_salt_p(&def_salt, key))
+ continue;
+ }
+ }
+ }
+ }
+ } else {
+ /*
+ * Pick the first key from the client's enctype list that is
+ * supported by the cryptosystem and by the given principal.
+ *
+ * RFC4120 says we SHOULD pick the first _strong_ key from the
+ * client's list... not the first key... If the admin disallows
+ * weak enctypes in krb5.conf and selects this key selection
+ * algorithm, then we get exactly what RFC4120 says.
+ */
+ for(i = 0; ret != 0 && i < len; i++) {
+
+ if (krb5_enctype_valid(r->context, etypes[i]) != 0 &&
+ !_kdc_is_weak_exception(princ->principal, etypes[i]))
+ continue;
+
+ key = NULL;
+ while (ret != 0 &&
+ hdb_next_enctype2key(r->context, princ, NULL,
+ etypes[i], &key) == 0) {
+ if (key->key.keyvalue.length == 0) {
+ ret = KRB5KDC_ERR_NULL_KEY;
+ continue;
+ }
+ enctype = etypes[i];
+ ret = 0;
+ if (is_preauth && ret_key != NULL &&
+ !is_good_salt_p(&def_salt, key))
+ continue;
+ }
+ }
+ }
+
+ if (ret == 0 && enctype == ETYPE_NULL) {
+ /*
+ * if the service principal is one for which there is a known 1DES
+ * exception and no other enctype matches both the client request and
+ * the service key list, provide a DES-CBC-CRC key.
+ */
+ if (ret_key == NULL &&
+ _kdc_is_weak_exception(princ->principal, ETYPE_DES_CBC_CRC)) {
+ ret = 0;
+ enctype = ETYPE_DES_CBC_CRC;
+ } else {
+ ret = KRB5KDC_ERR_ETYPE_NOSUPP;
+ }
+ }
+
+ if (ret == 0) {
+ if (ret_enctype != NULL)
+ *ret_enctype = enctype;
+ if (ret_key != NULL)
+ *ret_key = key;
+ if (ret_default_salt != NULL)
+ *ret_default_salt = is_default_salt_p(&def_salt, key);
+ }
+
+ krb5_free_salt (r->context, def_salt);
+ return ret;
+}
+
+/*
+ * The principal's session_etypes must be sorted in order of strength, with
+ * preferred etype first.
+*/
+krb5_error_code
+_kdc_find_session_etype(astgs_request_t r,
+ krb5_enctype *etypes, size_t len,
+ const hdb_entry *princ,
+ krb5_enctype *ret_enctype)
+{
+ size_t i;
+
+ if (princ->session_etypes == NULL) {
+ /* The principal must have session etypes available. */
+ return KRB5KDC_ERR_ETYPE_NOSUPP;
+ }
+
+ /* Loop over the client's specified etypes. */
+ for (i = 0; i < len; ++i) {
+ size_t j;
+
+ /* Check that the server also supports the etype. */
+ for (j = 0; j < princ->session_etypes->len; ++j) {
+ if (princ->session_etypes->val[j] == etypes[i]) {
+ *ret_enctype = etypes[i];
+ return 0;
+ }
+ }
+ }
+
+ return KRB5KDC_ERR_ETYPE_NOSUPP;
+}
+
+krb5_error_code
+_kdc_make_anonymous_principalname (PrincipalName *pn)
+{
+ pn->name_type = KRB5_NT_WELLKNOWN;
+ pn->name_string.len = 2;
+ pn->name_string.val = calloc(2, sizeof(*pn->name_string.val));
+ if (pn->name_string.val == NULL)
+ goto failed;
+
+ pn->name_string.val[0] = strdup(KRB5_WELLKNOWN_NAME);
+ if (pn->name_string.val[0] == NULL)
+ goto failed;
+
+ pn->name_string.val[1] = strdup(KRB5_ANON_NAME);
+ if (pn->name_string.val[1] == NULL)
+ goto failed;
+
+ return 0;
+
+failed:
+ free_PrincipalName(pn);
+
+ pn->name_type = KRB5_NT_UNKNOWN;
+ pn->name_string.len = 0;
+ pn->name_string.val = NULL;
+
+ return ENOMEM;
+}
+
+static void
+_kdc_r_log(astgs_request_t r, int level, const char *fmt, ...)
+ __attribute__ ((__format__ (__printf__, 3, 4)))
+{
+ va_list ap;
+ char *s;
+ va_start(ap, fmt);
+ s = kdc_log_msg_va(r->context, r->config, level, fmt, ap);
+ if(s) free(s);
+ va_end(ap);
+}
+
+void
+_kdc_set_const_e_text(astgs_request_t r, const char *e_text)
+{
+ /* We should never see this */
+ if (r->e_text) {
+ kdc_log(r->context, r->config, 1,
+ "trying to replace e-text \"%s\" with \"%s\"\n",
+ r->e_text, e_text);
+ return;
+ }
+
+ r->e_text = e_text;
+ kdc_log(r->context, r->config, 4, "%s", e_text);
+}
+
+void
+_kdc_set_e_text(astgs_request_t r, const char *fmt, ...)
+ __attribute__ ((__format__ (__printf__, 2, 3)))
+{
+ va_list ap;
+ char *e_text = NULL;
+ int vasprintf_ret;
+
+ va_start(ap, fmt);
+ vasprintf_ret = vasprintf(&e_text, fmt, ap);
+ va_end(ap);
+
+ if (vasprintf_ret < 0 || !e_text) {
+ /* not much else to do... */
+ kdc_log(r->context, r->config, 1,
+ "Could not set e_text: %s (out of memory)", fmt);
+ return;
+ }
+
+ /* We should never see this */
+ if (r->e_text) {
+ kdc_log(r->context, r->config, 1, "trying to replace e-text: %s\n",
+ e_text);
+ free(e_text);
+ return;
+ }
+
+ r->e_text = e_text;
+ r->e_text_buf = e_text;
+ kdc_log(r->context, r->config, 4, "%s", e_text);
+}
+
+void
+_kdc_log_timestamp(astgs_request_t r, const char *type,
+ KerberosTime authtime, KerberosTime *starttime,
+ KerberosTime endtime, KerberosTime *renew_till)
+{
+ krb5_kdc_configuration *config = r->config;
+ char authtime_str[100], starttime_str[100],
+ endtime_str[100], renewtime_str[100];
+
+ if (authtime)
+ kdc_audit_setkv_number((kdc_request_t)r, "auth", authtime);
+ if (starttime && *starttime)
+ kdc_audit_setkv_number((kdc_request_t)r, "start", *starttime);
+ if (endtime)
+ kdc_audit_setkv_number((kdc_request_t)r, "end", endtime);
+ if (renew_till && *renew_till)
+ kdc_audit_setkv_number((kdc_request_t)r, "renew", *renew_till);
+
+ krb5_format_time(r->context, authtime,
+ authtime_str, sizeof(authtime_str), TRUE);
+ if (starttime)
+ krb5_format_time(r->context, *starttime,
+ starttime_str, sizeof(starttime_str), TRUE);
+ else
+ strlcpy(starttime_str, "unset", sizeof(starttime_str));
+ krb5_format_time(r->context, endtime,
+ endtime_str, sizeof(endtime_str), TRUE);
+ if (renew_till)
+ krb5_format_time(r->context, *renew_till,
+ renewtime_str, sizeof(renewtime_str), TRUE);
+ else
+ strlcpy(renewtime_str, "unset", sizeof(renewtime_str));
+
+ kdc_log(r->context, config, 4,
+ "%s authtime: %s starttime: %s endtime: %s renew till: %s",
+ type, authtime_str, starttime_str, endtime_str, renewtime_str);
+}
+
+/*
+ *
+ */
+
+#ifdef PKINIT
+
+static krb5_error_code
+pa_pkinit_validate(astgs_request_t r, const PA_DATA *pa)
+{
+ pk_client_params *pkp = NULL;
+ char *client_cert = NULL;
+ krb5_error_code ret;
+
+ ret = _kdc_pk_rd_padata(r, pa, &pkp);
+ if (ret || pkp == NULL) {
+ if (ret == HX509_CERT_REVOKED) {
+ ret = KRB5_KDC_ERR_CLIENT_NOT_TRUSTED;
+ } else {
+ ret = KRB5KRB_AP_ERR_BAD_INTEGRITY;
+ }
+ _kdc_r_log(r, 4, "Failed to decode PKINIT PA-DATA -- %s",
+ r->cname);
+ goto out;
+ }
+
+ /* Validate the freshness token. */
+ ret = _kdc_pk_validate_freshness_token(r, pkp);
+ if (ret) {
+ _kdc_r_log(r, 4, "Failed to validate freshness token");
+ goto out;
+ }
+
+ ret = _kdc_pk_check_client(r, pkp, &client_cert);
+ if (client_cert)
+ kdc_audit_addkv((kdc_request_t)r, 0, KDC_REQUEST_KV_PKINIT_CLIENT_CERT,
+ "%s", client_cert);
+ if (ret) {
+ _kdc_set_e_text(r, "PKINIT certificate not allowed to "
+ "impersonate principal");
+ kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
+ KDC_AUTH_EVENT_CLIENT_NAME_UNAUTHORIZED);
+ goto out;
+ }
+
+ r->pa_endtime = _kdc_pk_endtime(pkp);
+ if (!r->client->flags.synthetic)
+ r->pa_max_life = _kdc_pk_max_life(pkp);
+
+ _kdc_r_log(r, 4, "PKINIT pre-authentication succeeded -- %s using %s",
+ r->cname, client_cert);
+
+ ret = _kdc_pk_mk_pa_reply(r, pkp);
+ if (ret) {
+ _kdc_set_e_text(r, "Failed to build PK-INIT reply");
+ goto out;
+ }
+ ret = _kdc_add_initial_verified_cas(r->context, r->config,
+ pkp, &r->et);
+
+ kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
+ KDC_AUTH_EVENT_PREAUTH_SUCCEEDED);
+
+ /*
+ * Match Windows by preferring the authenticator nonce over the one in the
+ * request body.
+ */
+ r->ek.nonce = _kdc_pk_nonce(pkp);
+
+ out:
+ if (pkp)
+ _kdc_pk_free_client_param(r->context, pkp);
+ free(client_cert);
+
+ return ret;
+}
+
+#endif /* PKINIT */
+
+static krb5_error_code
+pa_gss_validate(astgs_request_t r, const PA_DATA *pa)
+{
+ gss_client_params *gcp = NULL;
+ char *client_name = NULL;
+ krb5_error_code ret;
+ int open = 0;
+
+ ret = _kdc_gss_rd_padata(r, pa, &gcp, &open);
+ if (ret && gcp == NULL)
+ return ret;
+
+ if (open) {
+ ret = _kdc_gss_check_client(r, gcp, &client_name);
+ if (client_name)
+ kdc_audit_addkv((kdc_request_t)r, 0, KDC_REQUEST_KV_GSS_INITIATOR,
+ "%s", client_name);
+ if (ret) {
+ _kdc_set_e_text(r, "GSS-API client not allowed to "
+ "impersonate principal");
+ kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
+ KDC_AUTH_EVENT_CLIENT_NAME_UNAUTHORIZED);
+ goto out;
+ }
+
+ r->pa_endtime = _kdc_gss_endtime(r, gcp);
+
+ _kdc_r_log(r, 4, "GSS pre-authentication succeeded -- %s using %s",
+ r->cname, client_name);
+ kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
+ KDC_AUTH_EVENT_PREAUTH_SUCCEEDED);
+
+ ret = _kdc_gss_mk_composite_name_ad(r, gcp);
+ if (ret) {
+ _kdc_set_e_text(r, "Failed to build GSS authorization data");
+ goto out;
+ }
+ }
+
+ ret = _kdc_gss_mk_pa_reply(r, gcp);
+ if (ret) {
+ if (ret != KRB5_KDC_ERR_MORE_PREAUTH_DATA_REQUIRED)
+ _kdc_set_e_text(r, "Failed to build GSS pre-authentication reply");
+ goto out;
+ }
+
+ ret = kdc_request_set_attribute((kdc_request_t)r,
+ HSTR("org.h5l.pa-gss-client-params"), gcp);
+ if (ret)
+ goto out;
+
+out:
+ kdc_object_release(gcp);
+ free(client_name);
+
+ return ret;
+}
+
+static krb5_error_code
+pa_gss_finalize_pac(astgs_request_t r)
+{
+ gss_client_params *gcp;
+
+ gcp = kdc_request_get_attribute((kdc_request_t)r, HSTR("org.h5l.pa-gss-client-params"));
+
+ heim_assert(gcp != NULL, "invalid GSS-API client params");
+
+ return _kdc_gss_finalize_pac(r, gcp);
+}
+
+static krb5_error_code
+pa_enc_chal_decrypt_kvno(astgs_request_t r,
+ krb5_enctype aenctype,
+ krb5_data *pepper1client,
+ krb5_data *pepper1kdc,
+ krb5_data *pepper2,
+ krb5_kvno kvno,
+ EncryptedData *enc_data,
+ krb5_keyblock *KDCchallengekey,
+ struct Key **used_key)
+{
+ unsigned int invalidKeys = 0;
+ krb5_error_code ret;
+ const Keys *keys = NULL;
+ unsigned int i;
+
+ if (KDCchallengekey)
+ krb5_keyblock_zero(KDCchallengekey);
+ if (used_key)
+ *used_key = NULL;
+
+ keys = hdb_kvno2keys(r->context, r->client, kvno);
+ if (keys == NULL) {
+ return KRB5KDC_ERR_ETYPE_NOSUPP;
+ }
+
+ for (i = 0; i < keys->len; i++) {
+ struct Key *k = &keys->val[i];
+ krb5_crypto challengecrypto, longtermcrypto;
+ krb5_keyblock client_challengekey;
+
+ ret = krb5_crypto_init(r->context, &k->key, 0, &longtermcrypto);
+ if (ret)
+ continue;
+
+ ret = krb5_crypto_fx_cf2(r->context, r->armor_crypto, longtermcrypto,
+ pepper1client, pepper2, aenctype,
+ &client_challengekey);
+ if (ret) {
+ krb5_crypto_destroy(r->context, longtermcrypto);
+ continue;
+ }
+
+ ret = krb5_crypto_init(r->context, &client_challengekey, 0,
+ &challengecrypto);
+ krb5_free_keyblock_contents(r->context, &client_challengekey);
+ if (ret) {
+ krb5_crypto_destroy(r->context, longtermcrypto);
+ continue;
+ }
+
+ ret = _krb5_validate_pa_enc_challenge(r->context,
+ challengecrypto,
+ KRB5_KU_ENC_CHALLENGE_CLIENT,
+ enc_data,
+ r->cname);
+ krb5_crypto_destroy(r->context, challengecrypto);
+ if (ret) {
+ const char *msg;
+ krb5_error_code ret2;
+ char *str = NULL;
+
+ krb5_crypto_destroy(r->context, longtermcrypto);
+
+ if (ret != KRB5KRB_AP_ERR_BAD_INTEGRITY)
+ return ret;
+
+ invalidKeys += 1;
+
+ if (pepper1kdc == NULL)
+ /* The caller is not interessted in details */
+ continue;
+
+ ret2 = krb5_enctype_to_string(r->context, k->key.keytype, &str);
+ if (ret2)
+ str = NULL;
+ msg = krb5_get_error_message(r->context, ret);
+ _kdc_r_log(r, 2, "Failed to decrypt ENC-CHAL -- %s "
+ "(enctype %s) error %s",
+ r->cname, str ? str : "unknown enctype", msg);
+ krb5_free_error_message(r->context, msg);
+ free(str);
+
+ continue;
+ }
+
+ if (pepper1kdc == NULL) {
+ /* The caller is not interessted in details */
+ return 0;
+ }
+
+ heim_assert(KDCchallengekey != NULL,
+ "KDCchallengekey pointer required with pepper1kdc");
+ heim_assert(used_key != NULL,
+ "used_key pointer required with pepper1kdc");
+
+ /*
+ * Provide KDC authentication to the client, uses a different
+ * challenge key (different pepper).
+ */
+
+ ret = krb5_crypto_fx_cf2(r->context, r->armor_crypto, longtermcrypto,
+ pepper1kdc, pepper2, aenctype,
+ KDCchallengekey);
+ krb5_crypto_destroy(r->context, longtermcrypto);
+ if (ret)
+ return ret;
+
+ *used_key = k;
+ return 0;
+ }
+
+ if (invalidKeys == 0)
+ return KRB5KDC_ERR_ETYPE_NOSUPP;
+
+ return KRB5KDC_ERR_PREAUTH_FAILED;
+}
+
+static krb5_error_code
+pa_enc_chal_validate(astgs_request_t r, const PA_DATA *pa)
+{
+ krb5_kvno kvno = r->client->kvno;
+ krb5_data pepper1client, pepper1kdc, pepper2;
+ EncryptedData enc_data;
+ krb5_enctype aenctype;
+ krb5_error_code ret;
+ krb5_keyblock KDCchallengekey;
+ struct Key *k = NULL;
+ size_t size;
+
+ heim_assert(r->armor_crypto != NULL, "ENC-CHAL called for non FAST");
+
+ if (_kdc_is_anon_request(&r->req)) {
+ ret = KRB5KRB_AP_ERR_BAD_INTEGRITY;
+ kdc_log(r->context, r->config, 4, "ENC-CHAL doesn't support anon");
+ return ret;
+ }
+
+ if (r->client->flags.locked_out) {
+ ret = KRB5KDC_ERR_CLIENT_REVOKED;
+ kdc_log(r->context, r->config, 0,
+ "Client (%s) is locked out", r->cname);
+ kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
+ KDC_AUTH_EVENT_CLIENT_LOCKED_OUT);
+ return ret;
+ }
+
+ ret = decode_EncryptedData(pa->padata_value.data,
+ pa->padata_value.length,
+ &enc_data,
+ &size);
+ if (ret) {
+ ret = KRB5KRB_AP_ERR_BAD_INTEGRITY;
+ _kdc_r_log(r, 4, "Failed to decode PA-DATA -- %s",
+ r->cname);
+ return ret;
+ }
+
+ pepper1client.data = "clientchallengearmor";
+ pepper1client.length = strlen(pepper1client.data);
+ pepper1kdc.data = "kdcchallengearmor";
+ pepper1kdc.length = strlen(pepper1kdc.data);
+ pepper2.data = "challengelongterm";
+ pepper2.length = strlen(pepper2.data);
+
+ krb5_crypto_getenctype(r->context, r->armor_crypto, &aenctype);
+
+ kdc_log(r->context, r->config, 5, "FAST armor enctype is: %d", (int)aenctype);
+
+ ret = pa_enc_chal_decrypt_kvno(r, aenctype,
+ &pepper1client,
+ &pepper1kdc,
+ &pepper2,
+ kvno,
+ &enc_data,
+ &KDCchallengekey,
+ &k);
+ if (ret == KRB5KDC_ERR_ETYPE_NOSUPP) {
+ char *estr;
+ _kdc_set_e_text(r, "No key matching entype");
+ if(krb5_enctype_to_string(r->context, enc_data.etype, &estr))
+ estr = NULL;
+ if(estr == NULL)
+ _kdc_r_log(r, 4,
+ "No client key matching ENC-CHAL (%d) -- %s",
+ enc_data.etype, r->cname);
+ else
+ _kdc_r_log(r, 4,
+ "No client key matching ENC-CHAL (%s) -- %s",
+ estr, r->cname);
+ free(estr);
+ free_EncryptedData(&enc_data);
+ kdc_audit_setkv_number((kdc_request_t)r,
+ KDC_REQUEST_KV_PA_FAILED_KVNO,
+ kvno);
+ return ret;
+ }
+ if (ret == KRB5KRB_AP_ERR_SKEW) {
+ /*
+ * Logging happens inside of
+ * _krb5_validate_pa_enc_challenge()
+ * via pa_enc_chal_decrypt_kvno()
+ */
+
+ free_EncryptedData(&enc_data);
+ kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
+ KDC_AUTH_EVENT_CLIENT_TIME_SKEW);
+
+ /*
+ * The following is needed to make windows clients to
+ * retry using the timestamp in the error message, if
+ * there is a e_text, they become unhappy.
+ */
+ r->e_text = NULL;
+ return ret;
+ }
+ if (ret == KRB5KDC_ERR_PREAUTH_FAILED) {
+ krb5_error_code hret = ret;
+ int hi;
+
+ /*
+ * Logging happens inside of
+ * via pa_enc_chal_decrypt_kvno()
+ */
+
+ kdc_audit_setkv_number((kdc_request_t)r,
+ KDC_REQUEST_KV_PA_FAILED_KVNO,
+ kvno);
+
+ /*
+ * Check if old and older keys are
+ * able to decrypt.
+ */
+ for (hi = 1; hi < 3; hi++) {
+ krb5_kvno hkvno;
+
+ if (hi >= kvno) {
+ break;
+ }
+
+ hkvno = kvno - hi;
+ hret = pa_enc_chal_decrypt_kvno(r, aenctype,
+ &pepper1client,
+ NULL, /* pepper1kdc */
+ &pepper2,
+ hkvno,
+ &enc_data,
+ NULL, /* KDCchallengekey */
+ NULL); /* used_key */
+ if (hret == 0) {
+ kdc_audit_setkv_number((kdc_request_t)r,
+ KDC_REQUEST_KV_PA_HISTORIC_KVNO,
+ hkvno);
+ break;
+ }
+ if (hret == KRB5KDC_ERR_ETYPE_NOSUPP) {
+ break;
+ }
+ }
+
+ free_EncryptedData(&enc_data);
+
+ if (hret == 0)
+ kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
+ KDC_AUTH_EVENT_HISTORIC_LONG_TERM_KEY);
+ else
+ kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
+ KDC_AUTH_EVENT_WRONG_LONG_TERM_KEY);
+
+ return ret;
+ }
+ free_EncryptedData(&enc_data);
+ if (ret == 0) {
+ krb5_crypto challengecrypto;
+ char *estr = NULL;
+ char *astr = NULL;
+ char *kstr = NULL;
+
+ ret = krb5_crypto_init(r->context, &KDCchallengekey, 0, &challengecrypto);
+ krb5_free_keyblock_contents(r->context, &KDCchallengekey);
+ if (ret)
+ return ret;
+
+ ret = _krb5_make_pa_enc_challenge(r->context, challengecrypto,
+ KRB5_KU_ENC_CHALLENGE_KDC,
+ r->rep.padata);
+ krb5_crypto_destroy(r->context, challengecrypto);
+ if (ret)
+ return ret;
+
+ ret = set_salt_padata(r->context, r->config, r->rep.padata, k);
+ if (ret)
+ return ret;
+
+ /*
+ * Found a key that the client used, lets pick that as the reply key
+ */
+
+ krb5_free_keyblock_contents(r->context, &r->reply_key);
+ ret = krb5_copy_keyblock_contents(r->context, &k->key, &r->reply_key);
+ if (ret)
+ return ret;
+
+ if (krb5_enctype_to_string(r->context, (int)aenctype, &astr))
+ astr = NULL;
+ if (krb5_enctype_to_string(r->context, enc_data.etype, &estr))
+ estr = NULL;
+ if (krb5_enctype_to_string(r->context, k->key.keytype, &kstr))
+ kstr = NULL;
+ _kdc_r_log(r, 4, "ENC-CHAL Pre-authentication succeeded -- %s "
+ "using armor=%s enc=%s key=%s",
+ r->cname,
+ astr ? astr : "unknown enctype",
+ estr ? estr : "unknown enctype",
+ kstr ? kstr : "unknown enctype");
+ kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
+ KDC_AUTH_EVENT_VALIDATED_LONG_TERM_KEY);
+ kdc_audit_setkv_number((kdc_request_t)r,
+ KDC_REQUEST_KV_PA_SUCCEEDED_KVNO,
+ kvno);
+ return 0;
+ }
+
+ return ret;
+}
+
+static krb5_error_code
+pa_enc_ts_decrypt_kvno(astgs_request_t r,
+ krb5_kvno kvno,
+ const EncryptedData *enc_data,
+ krb5_data *ts_data,
+ Key **_pa_key)
+{
+ krb5_error_code ret;
+ krb5_crypto crypto;
+ Key *pa_key = NULL;
+ const Keys *keys = NULL;
+
+ if (_pa_key)
+ *_pa_key = NULL;
+
+ krb5_data_zero(ts_data);
+
+ keys = hdb_kvno2keys(r->context, r->client, kvno);
+ if (keys == NULL) {
+ return KRB5KDC_ERR_ETYPE_NOSUPP;
+ }
+ ret = hdb_enctype2key(r->context, r->client, keys,
+ enc_data->etype, &pa_key);
+ if(ret){
+ return KRB5KDC_ERR_ETYPE_NOSUPP;
+ }
+
+ try_next_key:
+ ret = krb5_crypto_init(r->context, &pa_key->key, 0, &crypto);
+ if (ret) {
+ const char *msg = krb5_get_error_message(r->context, ret);
+ _kdc_r_log(r, 4, "krb5_crypto_init failed: %s", msg);
+ krb5_free_error_message(r->context, msg);
+ return ret;
+ }
+
+ ret = krb5_decrypt_EncryptedData(r->context,
+ crypto,
+ KRB5_KU_PA_ENC_TIMESTAMP,
+ enc_data,
+ ts_data);
+ krb5_crypto_destroy(r->context, crypto);
+ /*
+ * Since the user might have several keys with the same
+ * enctype but with diffrent salting, we need to try all
+ * the keys with the same enctype.
+ */
+ if (ret) {
+ ret = hdb_next_enctype2key(r->context, r->client, keys,
+ enc_data->etype, &pa_key);
+ if (ret == 0)
+ goto try_next_key;
+
+ return KRB5KDC_ERR_PREAUTH_FAILED;
+ }
+
+ if (_pa_key)
+ *_pa_key = pa_key;
+ return 0;
+}
+
+static krb5_error_code
+pa_enc_ts_validate(astgs_request_t r, const PA_DATA *pa)
+{
+ krb5_kvno kvno = r->client->kvno;
+ EncryptedData enc_data;
+ krb5_error_code ret;
+ krb5_data ts_data;
+ PA_ENC_TS_ENC p;
+ size_t len;
+ Key *pa_key;
+ char *str;
+
+ if (r->armor_crypto && !r->config->enable_armored_pa_enc_timestamp) {
+ ret = KRB5KDC_ERR_POLICY;
+ kdc_log(r->context, r->config, 0,
+ "Armored encrypted timestamp pre-authentication is disabled");
+ return ret;
+ } else if (!r->armor_crypto && !r->config->enable_unarmored_pa_enc_timestamp) {
+ ret = KRB5KDC_ERR_POLICY;
+ kdc_log(r->context, r->config, 0,
+ "Unarmored encrypted timestamp pre-authentication is disabled");
+ return ret;
+ }
+
+ if (r->client->flags.locked_out) {
+ ret = KRB5KDC_ERR_CLIENT_REVOKED;
+ kdc_log(r->context, r->config, 0,
+ "Client (%s) is locked out", r->cname);
+ kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
+ KDC_AUTH_EVENT_CLIENT_LOCKED_OUT);
+ return ret;
+ }
+
+ ret = decode_EncryptedData(pa->padata_value.data,
+ pa->padata_value.length,
+ &enc_data,
+ &len);
+ if (ret) {
+ ret = KRB5KRB_AP_ERR_BAD_INTEGRITY;
+ _kdc_r_log(r, 4, "Failed to decode PA-DATA -- %s",
+ r->cname);
+ goto out;
+ }
+
+ ret = pa_enc_ts_decrypt_kvno(r, kvno, &enc_data, &ts_data, &pa_key);
+ if (ret == KRB5KDC_ERR_ETYPE_NOSUPP) {
+ char *estr;
+ _kdc_set_e_text(r, "No key matching enctype");
+ if(krb5_enctype_to_string(r->context, enc_data.etype, &estr))
+ estr = NULL;
+ if(estr == NULL)
+ _kdc_r_log(r, 4,
+ "No client key matching pa-data (%d) -- %s",
+ enc_data.etype, r->cname);
+ else
+ _kdc_r_log(r, 4,
+ "No client key matching pa-data (%s) -- %s",
+ estr, r->cname);
+ free(estr);
+ free_EncryptedData(&enc_data);
+ kdc_audit_setkv_number((kdc_request_t)r,
+ KDC_REQUEST_KV_PA_FAILED_KVNO,
+ kvno);
+ goto out;
+ }
+
+ if (ret == KRB5KDC_ERR_PREAUTH_FAILED) {
+ krb5_error_code ret2;
+ const char *msg = krb5_get_error_message(r->context, ret);
+ krb5_error_code hret = ret;
+ int hi;
+
+ kdc_audit_setkv_number((kdc_request_t)r,
+ KDC_REQUEST_KV_PA_FAILED_KVNO,
+ kvno);
+
+ /*
+ * Check if old and older keys are
+ * able to decrypt.
+ */
+ for (hi = 1; hi < 3; hi++) {
+ krb5_kvno hkvno;
+
+ if (hi >= kvno) {
+ break;
+ }
+
+ hkvno = kvno - hi;
+ hret = pa_enc_ts_decrypt_kvno(r, hkvno,
+ &enc_data,
+ &ts_data,
+ NULL); /* pa_key */
+ if (hret == 0) {
+ krb5_data_free(&ts_data);
+ kdc_audit_setkv_number((kdc_request_t)r,
+ KDC_REQUEST_KV_PA_HISTORIC_KVNO,
+ hkvno);
+ break;
+ }
+ if (hret == KRB5KDC_ERR_ETYPE_NOSUPP) {
+ break;
+ }
+ }
+
+ ret2 = krb5_enctype_to_string(r->context, enc_data.etype, &str);
+ if (ret2)
+ str = NULL;
+ _kdc_r_log(r, 2, "Failed to decrypt PA-DATA -- %s "
+ "(enctype %s) error %s",
+ r->cname, str ? str : "unknown enctype", msg);
+ krb5_xfree(str);
+ krb5_free_error_message(r->context, msg);
+ kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_PA_ETYPE,
+ enc_data.etype);
+ if (hret == 0)
+ kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
+ KDC_AUTH_EVENT_HISTORIC_LONG_TERM_KEY);
+ else
+ kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
+ KDC_AUTH_EVENT_WRONG_LONG_TERM_KEY);
+
+ free_EncryptedData(&enc_data);
+
+ ret = KRB5KDC_ERR_PREAUTH_FAILED;
+ goto out;
+ }
+ free_EncryptedData(&enc_data);
+ ret = decode_PA_ENC_TS_ENC(ts_data.data,
+ ts_data.length,
+ &p,
+ &len);
+ krb5_data_free(&ts_data);
+ if(ret){
+ ret = KRB5KDC_ERR_PREAUTH_FAILED;
+ _kdc_r_log(r, 4, "Failed to decode PA-ENC-TS-ENC -- %s",
+ r->cname);
+ goto out;
+ }
+ if (labs(kdc_time - p.patimestamp) > r->context->max_skew) {
+ char client_time[100];
+
+ krb5_format_time(r->context, p.patimestamp,
+ client_time, sizeof(client_time), TRUE);
+
+ ret = KRB5KRB_AP_ERR_SKEW;
+ _kdc_r_log(r, 4, "Too large time skew, "
+ "client time %s is out by %u > %u seconds -- %s",
+ client_time,
+ (unsigned)labs(kdc_time - p.patimestamp),
+ r->context->max_skew,
+ r->cname);
+ kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
+ KDC_AUTH_EVENT_CLIENT_TIME_SKEW);
+
+ /*
+ * The following is needed to make windows clients to
+ * retry using the timestamp in the error message, if
+ * there is a e_text, they become unhappy.
+ */
+ r->e_text = NULL;
+ free_PA_ENC_TS_ENC(&p);
+ goto out;
+ }
+ free_PA_ENC_TS_ENC(&p);
+
+ ret = set_salt_padata(r->context, r->config, r->rep.padata, pa_key);
+ if (ret == 0)
+ ret = krb5_copy_keyblock_contents(r->context, &pa_key->key, &r->reply_key);
+ if (ret)
+ return ret;
+
+ ret = krb5_enctype_to_string(r->context, pa_key->key.keytype, &str);
+ if (ret)
+ str = NULL;
+ _kdc_r_log(r, 4, "ENC-TS Pre-authentication succeeded -- %s using %s",
+ r->cname, str ? str : "unknown enctype");
+ krb5_xfree(str);
+ kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_PA_ETYPE,
+ pa_key->key.keytype);
+ kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
+ KDC_AUTH_EVENT_VALIDATED_LONG_TERM_KEY);
+ kdc_audit_setkv_number((kdc_request_t)r,
+ KDC_REQUEST_KV_PA_SUCCEEDED_KVNO,
+ kvno);
+
+ ret = 0;
+
+ out:
+
+ return ret;
+}
+
+#ifdef PKINIT
+
+static krb5_error_code
+make_freshness_token(astgs_request_t r, const Key *krbtgt_key, unsigned krbtgt_kvno)
+{
+ krb5_error_code ret = 0;
+ const struct timeval current_kdc_time = krb5_kdc_get_time();
+ int usec = current_kdc_time.tv_usec;
+ const PA_ENC_TS_ENC ts_enc = {
+ .patimestamp = current_kdc_time.tv_sec,
+ .pausec = &usec,
+ };
+ unsigned char *encoded_ts_enc = NULL;
+ size_t ts_enc_size;
+ size_t ts_enc_len = 0;
+ EncryptedData encdata;
+ krb5_crypto crypto;
+ unsigned char *token = NULL;
+ size_t token_size;
+ size_t token_len = 0;
+ size_t token_alloc_size;
+
+ ASN1_MALLOC_ENCODE(PA_ENC_TS_ENC,
+ encoded_ts_enc,
+ ts_enc_size,
+ &ts_enc,
+ &ts_enc_len,
+ ret);
+ if (ret)
+ return ret;
+ if (ts_enc_size != ts_enc_len)
+ krb5_abortx(r->context, "internal error in ASN.1 encoder");
+
+ ret = krb5_crypto_init(r->context, &krbtgt_key->key, 0, &crypto);
+ if (ret) {
+ free(encoded_ts_enc);
+ return ret;
+ }
+
+ ret = krb5_encrypt_EncryptedData(r->context,
+ crypto,
+ KRB5_KU_AS_FRESHNESS,
+ encoded_ts_enc,
+ ts_enc_len,
+ krbtgt_kvno,
+ &encdata);
+ free(encoded_ts_enc);
+ krb5_crypto_destroy(r->context, crypto);
+ if (ret)
+ return ret;
+
+ token_size = length_EncryptedData(&encdata);
+ token_alloc_size = token_size + 2; /* Account for the two leading zero bytes. */
+ token = calloc(1, token_alloc_size);
+ if (token == NULL) {
+ free_EncryptedData(&encdata);
+ return ENOMEM;
+ }
+
+ ret = encode_EncryptedData(token + token_alloc_size - 1,
+ token_size,
+ &encdata,
+ &token_len);
+ free_EncryptedData(&encdata);
+ if (ret) {
+ free(token);
+ return ret;
+ }
+ if (token_size != token_len)
+ krb5_abortx(r->context, "internal error in ASN.1 encoder");
+
+ ret = krb5_padata_add(r->context,
+ r->rep.padata,
+ KRB5_PADATA_AS_FRESHNESS,
+ token,
+ token_alloc_size);
+ if (ret)
+ free(token);
+ return ret;
+}
+
+#endif /* PKINIT */
+
+static krb5_error_code
+send_freshness_token(astgs_request_t r, const Key *krbtgt_key, unsigned krbtgt_kvno)
+{
+ krb5_error_code ret = 0;
+#ifdef PKINIT
+ int idx = 0;
+ const PA_DATA *freshness_padata = NULL;
+
+ freshness_padata = _kdc_find_padata(&r->req,
+ &idx,
+ KRB5_PADATA_AS_FRESHNESS);
+ if (freshness_padata == NULL) {
+ return 0;
+ }
+
+ ret = make_freshness_token(r, krbtgt_key, krbtgt_kvno);
+#endif /* PKINIT */
+ return ret;
+}
+
+struct kdc_patypes {
+ int type;
+ const char *name;
+ unsigned int flags;
+#define PA_ANNOUNCE 1
+#define PA_REQ_FAST 2 /* only use inside fast */
+#define PA_SYNTHETIC_OK 4
+#define PA_REPLACE_REPLY_KEY 8 /* PA mech replaces reply key */
+#define PA_USES_LONG_TERM_KEY 16 /* PA mech uses client's long-term key */
+#define PA_USES_FAST_COOKIE 32 /* Multi-step PA mech maintains state in PA-FX-COOKIE */
+ krb5_error_code (*validate)(astgs_request_t, const PA_DATA *pa);
+ krb5_error_code (*finalize_pac)(astgs_request_t r);
+ void (*cleanup)(astgs_request_t r);
+};
+
+static const struct kdc_patypes pat[] = {
+#ifdef PKINIT
+ {
+ KRB5_PADATA_PK_AS_REQ, "PK-INIT(ietf)",
+ PA_ANNOUNCE | PA_SYNTHETIC_OK | PA_REPLACE_REPLY_KEY,
+ pa_pkinit_validate, NULL, NULL
+ },
+ {
+ KRB5_PADATA_PK_AS_REQ_WIN, "PK-INIT(win2k)", PA_ANNOUNCE | PA_REPLACE_REPLY_KEY,
+ pa_pkinit_validate, NULL, NULL
+ },
+ {
+ KRB5_PADATA_PKINIT_KX, "Anonymous PK-INIT", PA_ANNOUNCE,
+ NULL, NULL, NULL
+ },
+#else
+ { KRB5_PADATA_PK_AS_REQ, "PK-INIT(ietf)", 0, NULL , NULL, NULL },
+ { KRB5_PADATA_PK_AS_REQ_WIN, "PK-INIT(win2k)", 0, NULL, NULL, NULL },
+ { KRB5_PADATA_PKINIT_KX, "Anonymous PK-INIT", 0, NULL, NULL, NULL },
+#endif
+ { KRB5_PADATA_PA_PK_OCSP_RESPONSE , "OCSP", 0, NULL, NULL, NULL },
+ {
+ KRB5_PADATA_ENC_TIMESTAMP , "ENC-TS",
+ PA_ANNOUNCE | PA_USES_LONG_TERM_KEY,
+ pa_enc_ts_validate, NULL, NULL
+ },
+ {
+ KRB5_PADATA_ENCRYPTED_CHALLENGE , "ENC-CHAL",
+ PA_ANNOUNCE | PA_USES_LONG_TERM_KEY | PA_REQ_FAST,
+ pa_enc_chal_validate, NULL, NULL
+ },
+ { KRB5_PADATA_REQ_ENC_PA_REP , "REQ-ENC-PA-REP", 0, NULL, NULL, NULL },
+ { KRB5_PADATA_FX_FAST, "FX-FAST", PA_ANNOUNCE, NULL, NULL, NULL },
+ { KRB5_PADATA_FX_ERROR, "FX-ERROR", 0, NULL, NULL, NULL },
+ { KRB5_PADATA_FX_COOKIE, "FX-COOKIE", 0, NULL, NULL, NULL },
+ {
+ KRB5_PADATA_GSS , "GSS",
+ PA_ANNOUNCE | PA_SYNTHETIC_OK | PA_REPLACE_REPLY_KEY | PA_USES_FAST_COOKIE,
+ pa_gss_validate, pa_gss_finalize_pac, NULL
+ },
+};
+
+static void
+log_patypes(astgs_request_t r, METHOD_DATA *padata)
+{
+ krb5_kdc_configuration *config = r->config;
+ struct rk_strpool *p = NULL;
+ char *str;
+ size_t n, m;
+
+ for (n = 0; n < padata->len; n++) {
+ for (m = 0; m < sizeof(pat) / sizeof(pat[0]); m++) {
+ if (padata->val[n].padata_type == pat[m].type) {
+ p = rk_strpoolprintf(p, "%s", pat[m].name);
+ break;
+ }
+ }
+ if (m == sizeof(pat) / sizeof(pat[0]))
+ p = rk_strpoolprintf(p, "%d", padata->val[n].padata_type);
+ if (p && n + 1 < padata->len)
+ p = rk_strpoolprintf(p, ", ");
+ if (p == NULL) {
+ kdc_log(r->context, config, 1, "out of memory");
+ return;
+ }
+ }
+ if (p == NULL)
+ p = rk_strpoolprintf(p, "none");
+
+ str = rk_strpoolcollect(p);
+ kdc_log(r->context, config, 4, "Client sent patypes: %s", str);
+ kdc_audit_addkv((kdc_request_t)r, KDC_AUDIT_EATWHITE,
+ "client-pa", "%s", str);
+ free(str);
+}
+
+static krb5_boolean
+pa_used_flag_isset(astgs_request_t r, unsigned int flag)
+{
+ if (r->pa_used == NULL)
+ return FALSE;
+
+ return (r->pa_used->flags & flag) == flag;
+}
+
+/*
+ *
+ */
+
+krb5_error_code
+_kdc_encode_reply(krb5_context context,
+ krb5_kdc_configuration *config,
+ astgs_request_t r, uint32_t nonce,
+ krb5_enctype etype,
+ int skvno, const EncryptionKey *skey,
+ int ckvno,
+ int rk_is_subkey,
+ krb5_data *reply)
+{
+ unsigned char *buf;
+ size_t buf_size;
+ size_t len = 0;
+ krb5_error_code ret;
+ krb5_crypto crypto;
+ KDC_REP *rep = &r->rep;
+ EncTicketPart *et = &r->et;
+ EncKDCRepPart *ek = &r->ek;
+
+ heim_assert(rep->padata != NULL, "reply padata uninitialized");
+
+ ASN1_MALLOC_ENCODE(EncTicketPart, buf, buf_size, et, &len, ret);
+ if(ret) {
+ const char *msg = krb5_get_error_message(context, ret);
+ kdc_log(context, config, 4, "Failed to encode ticket: %s", msg);
+ krb5_free_error_message(context, msg);
+ return ret;
+ }
+ if(buf_size != len)
+ krb5_abortx(context, "Internal error in ASN.1 encoder");
+
+ ret = krb5_crypto_init(context, skey, etype, &crypto);
+ if (ret) {
+ const char *msg = krb5_get_error_message(context, ret);
+ kdc_log(context, config, 4, "krb5_crypto_init failed: %s", msg);
+ krb5_free_error_message(context, msg);
+ free(buf);
+ return ret;
+ }
+
+ ret = krb5_encrypt_EncryptedData(context,
+ crypto,
+ KRB5_KU_TICKET,
+ buf,
+ len,
+ skvno,
+ &rep->ticket.enc_part);
+ free(buf);
+ krb5_crypto_destroy(context, crypto);
+ if(ret) {
+ const char *msg = krb5_get_error_message(context, ret);
+ kdc_log(context, config, 4, "Failed to encrypt data: %s", msg);
+ krb5_free_error_message(context, msg);
+ return ret;
+ }
+
+ if (r && r->armor_crypto) {
+ KrbFastFinished finished;
+ krb5_data data;
+
+ kdc_log(context, config, 4, "FAST armor protection");
+
+ memset(&finished, 0, sizeof(finished));
+ krb5_data_zero(&data);
+
+ finished.timestamp = kdc_time;
+ finished.usec = 0;
+ finished.crealm = et->crealm;
+ finished.cname = et->cname;
+
+ ASN1_MALLOC_ENCODE(Ticket, data.data, data.length,
+ &rep->ticket, &len, ret);
+ if (ret)
+ return ret;
+ if (data.length != len)
+ krb5_abortx(context, "internal asn.1 error");
+
+ ret = krb5_create_checksum(context, r->armor_crypto,
+ KRB5_KU_FAST_FINISHED, 0,
+ data.data, data.length,
+ &finished.ticket_checksum);
+ krb5_data_free(&data);
+ if (ret)
+ return ret;
+
+ ret = _kdc_fast_mk_response(context, r->armor_crypto,
+ rep->padata, &r->strengthen_key, &finished,
+ nonce, &data);
+ free_Checksum(&finished.ticket_checksum);
+ if (ret)
+ return ret;
+
+ free_METHOD_DATA(r->rep.padata);
+
+ ret = krb5_padata_add(context, rep->padata,
+ KRB5_PADATA_FX_FAST,
+ data.data, data.length);
+ if (ret)
+ return ret;
+
+ /*
+ * Hide client name for privacy reasons
+ */
+ if (r->fast.flags.requested_hidden_names) {
+ Realm anon_realm = KRB5_ANON_REALM;
+
+ free_Realm(&rep->crealm);
+ ret = copy_Realm(&anon_realm, &rep->crealm);
+ if (ret == 0) {
+ free_PrincipalName(&rep->cname);
+ ret = _kdc_make_anonymous_principalname(&rep->cname);
+ }
+ if (ret)
+ return ret;
+ }
+ }
+
+ if (rep->padata->len == 0) {
+ free_METHOD_DATA(rep->padata);
+ free(rep->padata);
+ rep->padata = NULL;
+ }
+
+ if(rep->msg_type == krb_as_rep && !config->encode_as_rep_as_tgs_rep)
+ ASN1_MALLOC_ENCODE(EncASRepPart, buf, buf_size, ek, &len, ret);
+ else
+ ASN1_MALLOC_ENCODE(EncTGSRepPart, buf, buf_size, ek, &len, ret);
+ if(ret) {
+ const char *msg = krb5_get_error_message(context, ret);
+ kdc_log(context, config, 4, "Failed to encode KDC-REP: %s", msg);
+ krb5_free_error_message(context, msg);
+ return ret;
+ }
+ if(buf_size != len) {
+ free(buf);
+ kdc_log(context, config, 4, "Internal error in ASN.1 encoder");
+ _kdc_set_e_text(r, "KDC internal error");
+ return KRB5KRB_ERR_GENERIC;
+ }
+ ret = krb5_crypto_init(context, &r->reply_key, 0, &crypto);
+ if (ret) {
+ const char *msg = krb5_get_error_message(context, ret);
+ free(buf);
+ kdc_log(context, config, 4, "krb5_crypto_init failed: %s", msg);
+ krb5_free_error_message(context, msg);
+ return ret;
+ }
+ if(rep->msg_type == krb_as_rep) {
+ ret = krb5_encrypt_EncryptedData(context,
+ crypto,
+ KRB5_KU_AS_REP_ENC_PART,
+ buf,
+ len,
+ ckvno,
+ &rep->enc_part);
+ free(buf);
+ if (ret == 0)
+ ASN1_MALLOC_ENCODE(AS_REP, buf, buf_size, rep, &len, ret);
+ } else {
+ ret = krb5_encrypt_EncryptedData(context,
+ crypto,
+ rk_is_subkey ?
+ KRB5_KU_TGS_REP_ENC_PART_SUB_KEY :
+ KRB5_KU_TGS_REP_ENC_PART_SESSION,
+ buf,
+ len,
+ ckvno,
+ &rep->enc_part);
+ free(buf);
+ if (ret == 0)
+ ASN1_MALLOC_ENCODE(TGS_REP, buf, buf_size, rep, &len, ret);
+ }
+ krb5_crypto_destroy(context, crypto);
+ if(ret) {
+ const char *msg = krb5_get_error_message(context, ret);
+ kdc_log(context, config, 4, "Failed to encode KDC-REP: %s", msg);
+ krb5_free_error_message(context, msg);
+ return ret;
+ }
+ if(buf_size != len) {
+ free(buf);
+ kdc_log(context, config, 4, "Internal error in ASN.1 encoder");
+ _kdc_set_e_text(r, "KDC internal error");
+ return KRB5KRB_ERR_GENERIC;
+ }
+ reply->data = buf;
+ reply->length = buf_size;
+ return 0;
+}
+
+/*
+ *
+ */
+
+static krb5_error_code
+get_pa_etype_info(krb5_context context,
+ krb5_kdc_configuration *config,
+ METHOD_DATA *md, Key *ckey,
+ krb5_boolean include_salt)
+{
+ krb5_error_code ret = 0;
+ ETYPE_INFO_ENTRY eie; /* do not free this one */
+ ETYPE_INFO ei;
+ PA_DATA pa;
+ size_t len;
+
+ /*
+ * Code moved here from what used to be make_etype_info_entry() because
+ * using the ASN.1 compiler-generated SEQUENCE OF add functions makes that
+ * old function's body and this one's small and clean.
+ *
+ * The following comment blocks were there:
+ *
+ * According to `the specs', we can't send a salt if we have AFS3 salted
+ * key, but that requires that you *know* what cell you are using (e.g by
+ * assuming that the cell is the same as the realm in lower case)
+ *
+ * We shouldn't sent salttype since it is incompatible with the
+ * specification and it breaks windows clients. The afs salting problem
+ * is solved by using KRB5-PADATA-AFS3-SALT implemented in Heimdal 0.7 and
+ * later.
+ *
+ * We return no salt type at all, as that should indicate the default salt
+ * type and make everybody happy. some systems (like w2k) dislike being
+ * told the salt type here.
+ */
+
+ pa.padata_type = KRB5_PADATA_ETYPE_INFO;
+ pa.padata_value.data = NULL;
+ pa.padata_value.length = 0;
+ ei.len = 0;
+ ei.val = NULL;
+ eie.etype = ckey->key.keytype;
+ eie.salttype = NULL;
+ eie.salt = NULL;
+ if (include_salt && ckey->salt)
+ eie.salt = &ckey->salt->salt;
+ ret = add_ETYPE_INFO(&ei, &eie);
+ if (ret == 0)
+ ASN1_MALLOC_ENCODE(ETYPE_INFO, pa.padata_value.data, pa.padata_value.length,
+ &ei, &len, ret);
+ if (ret == 0)
+ add_METHOD_DATA(md, &pa);
+ free_ETYPE_INFO(&ei);
+ free_PA_DATA(&pa);
+ return ret;
+}
+
+/*
+ *
+ */
+
+extern const int _krb5_AES_SHA1_string_to_default_iterator;
+extern const int _krb5_AES_SHA2_string_to_default_iterator;
+
+static krb5_error_code
+make_s2kparams(int value, size_t len, krb5_data **ps2kparams)
+{
+ krb5_data *s2kparams;
+ krb5_error_code ret;
+
+ ALLOC(s2kparams);
+ if (s2kparams == NULL)
+ return ENOMEM;
+ ret = krb5_data_alloc(s2kparams, len);
+ if (ret) {
+ free(s2kparams);
+ return ret;
+ }
+ _krb5_put_int(s2kparams->data, value, len);
+ *ps2kparams = s2kparams;
+ return 0;
+}
+
+static krb5_error_code
+make_etype_info2_entry(ETYPE_INFO2_ENTRY *ent,
+ Key *key,
+ krb5_boolean include_salt)
+{
+ krb5_error_code ret;
+
+ ent->etype = key->key.keytype;
+ if (key->salt && include_salt) {
+ ALLOC(ent->salt);
+ if (ent->salt == NULL)
+ return ENOMEM;
+ *ent->salt = malloc(key->salt->salt.length + 1);
+ if (*ent->salt == NULL) {
+ free(ent->salt);
+ ent->salt = NULL;
+ return ENOMEM;
+ }
+ memcpy(*ent->salt, key->salt->salt.data, key->salt->salt.length);
+ (*ent->salt)[key->salt->salt.length] = '\0';
+ } else
+ ent->salt = NULL;
+
+ ent->s2kparams = NULL;
+
+ switch (key->key.keytype) {
+ case ETYPE_AES128_CTS_HMAC_SHA1_96:
+ case ETYPE_AES256_CTS_HMAC_SHA1_96:
+ ret = make_s2kparams(_krb5_AES_SHA1_string_to_default_iterator,
+ 4, &ent->s2kparams);
+ break;
+ case KRB5_ENCTYPE_AES128_CTS_HMAC_SHA256_128:
+ case KRB5_ENCTYPE_AES256_CTS_HMAC_SHA384_192:
+ ret = make_s2kparams(_krb5_AES_SHA2_string_to_default_iterator,
+ 4, &ent->s2kparams);
+ break;
+ case ETYPE_DES_CBC_CRC:
+ case ETYPE_DES_CBC_MD4:
+ case ETYPE_DES_CBC_MD5:
+ /* Check if this was a AFS3 salted key */
+ if(key->salt && key->salt->type == hdb_afs3_salt)
+ ret = make_s2kparams(1, 1, &ent->s2kparams);
+ else
+ ret = 0;
+ break;
+ default:
+ ret = 0;
+ break;
+ }
+ return ret;
+}
+
+/*
+ * Return an ETYPE-INFO2. Enctypes are storted the same way as in the
+ * database (client supported enctypes first, then the unsupported
+ * enctypes).
+ */
+
+static krb5_error_code
+get_pa_etype_info2(krb5_context context,
+ krb5_kdc_configuration *config,
+ METHOD_DATA *md, Key *ckey,
+ krb5_boolean include_salt)
+{
+ krb5_error_code ret = 0;
+ ETYPE_INFO2 pa;
+ unsigned char *buf;
+ size_t len;
+
+ pa.len = 1;
+ pa.val = calloc(1, sizeof(pa.val[0]));
+ if(pa.val == NULL)
+ return ENOMEM;
+
+ ret = make_etype_info2_entry(&pa.val[0], ckey, include_salt);
+ if (ret) {
+ free_ETYPE_INFO2(&pa);
+ return ret;
+ }
+
+ ASN1_MALLOC_ENCODE(ETYPE_INFO2, buf, len, &pa, &len, ret);
+ free_ETYPE_INFO2(&pa);
+ if(ret)
+ return ret;
+ ret = realloc_method_data(md);
+ if(ret) {
+ free(buf);
+ return ret;
+ }
+ md->val[md->len - 1].padata_type = KRB5_PADATA_ETYPE_INFO2;
+ md->val[md->len - 1].padata_value.length = len;
+ md->val[md->len - 1].padata_value.data = buf;
+ return 0;
+}
+
+/*
+ * Return 0 if the client has only older enctypes, this is for
+ * determining if the server should send ETYPE_INFO2 or not.
+ */
+
+static int
+newer_enctype_present(krb5_context context,
+ struct KDC_REQ_BODY_etype *etype_list)
+{
+ size_t i;
+
+ for (i = 0; i < etype_list->len; i++) {
+ if (!krb5_is_enctype_old(context, etype_list->val[i]))
+ return 1;
+ }
+ return 0;
+}
+
+static krb5_error_code
+get_pa_etype_info_both(krb5_context context,
+ krb5_kdc_configuration *config,
+ struct KDC_REQ_BODY_etype *etype_list,
+ METHOD_DATA *md, Key *ckey,
+ krb5_boolean include_salt)
+{
+ krb5_error_code ret;
+
+ /*
+ * Windows 2019 (and earlier versions) always sends the salt
+ * and Samba has testsuites that check this behaviour, so a
+ * Samba AD DC will set this flag to match the AS-REP packet
+ * more closely.
+ */
+ if (config->force_include_pa_etype_salt)
+ include_salt = TRUE;
+
+ /*
+ * RFC4120 requires:
+ * When the AS server is to include pre-authentication data in a
+ * KRB-ERROR or in an AS-REP, it MUST use PA-ETYPE-INFO2, not
+ * PA-ETYPE-INFO, if the etype field of the client's AS-REQ lists
+ * at least one "newer" encryption type. Otherwise (when the etype
+ * field of the client's AS-REQ does not list any "newer" encryption
+ * types), it MUST send both PA-ETYPE-INFO2 and PA-ETYPE-INFO (both
+ * with an entry for each enctype). A "newer" enctype is any enctype
+ * first officially specified concurrently with or subsequent to the
+ * issue of this RFC. The enctypes DES, 3DES, or RC4 and any defined
+ * in [RFC1510] are not "newer" enctypes.
+ *
+ * It goes on to state:
+ * The preferred ordering of the "hint" pre-authentication data that
+ * affect client key selection is: ETYPE-INFO2, followed by ETYPE-INFO,
+ * followed by PW-SALT. As noted in Section 3.1.3, a KDC MUST NOT send
+ * ETYPE-INFO or PW-SALT when the client's AS-REQ includes at least one
+ * "newer" etype.
+ */
+
+ ret = get_pa_etype_info2(context, config, md, ckey, include_salt);
+ if (ret)
+ return ret;
+
+ if (!newer_enctype_present(context, etype_list))
+ ret = get_pa_etype_info(context, config, md, ckey, include_salt);
+
+ return ret;
+}
+
+/*
+ *
+ */
+
+void
+_log_astgs_req(astgs_request_t r, krb5_enctype setype)
+{
+ const KDC_REQ_BODY *b = &r->req.req_body;
+ krb5_enctype cetype = r->reply_key.keytype;
+ krb5_error_code ret;
+ struct rk_strpool *p;
+ struct rk_strpool *s = NULL;
+ char *str;
+ char *cet;
+ char *set;
+ size_t i;
+
+ /*
+ * we are collecting ``p'' and ``s''. The former is a textual
+ * representation of the enctypes as strings which will be used
+ * for debugging. The latter is a terse comma separated list of
+ * the %d's of the enctypes to emit into our audit trail to
+ * conserve space in the logs.
+ */
+
+ p = rk_strpoolprintf(NULL, "%s", "Client supported enctypes: ");
+
+ for (i = 0; i < b->etype.len; i++) {
+ ret = krb5_enctype_to_string(r->context, b->etype.val[i], &str);
+ if (ret == 0) {
+ p = rk_strpoolprintf(p, "%s", str);
+ free(str);
+ } else
+ p = rk_strpoolprintf(p, "%d", b->etype.val[i]);
+ if (p == NULL) {
+ rk_strpoolfree(s);
+ _kdc_r_log(r, 4, "out of memory");
+ return;
+ }
+ s = rk_strpoolprintf(s, "%d", b->etype.val[i]);
+ if (i + 1 < b->etype.len) {
+ p = rk_strpoolprintf(p, ", ");
+ s = rk_strpoolprintf(s, ",");
+ }
+ }
+ if (p == NULL)
+ p = rk_strpoolprintf(p, "no encryption types");
+
+ str = rk_strpoolcollect(s);
+ if (str)
+ kdc_audit_addkv((kdc_request_t)r, KDC_AUDIT_EATWHITE, "etypes", "%s",
+ str);
+ free(str);
+
+ ret = krb5_enctype_to_string(r->context, cetype, &cet);
+ if(ret == 0) {
+ ret = krb5_enctype_to_string(r->context, setype, &set);
+ if (ret == 0) {
+ p = rk_strpoolprintf(p, ", using %s/%s", cet, set);
+ free(set);
+ }
+ free(cet);
+ }
+ if (ret != 0)
+ p = rk_strpoolprintf(p, ", using enctypes %d/%d",
+ cetype, setype);
+
+ str = rk_strpoolcollect(p);
+ if (str)
+ _kdc_r_log(r, 4, "%s", str);
+ free(str);
+
+ kdc_audit_addkv((kdc_request_t)r, 0, "etype", "%d/%d", cetype, setype);
+
+ {
+ char fixedstr[128];
+ int result;
+
+ result = unparse_flags(KDCOptions2int(b->kdc_options), asn1_KDCOptions_units(),
+ fixedstr, sizeof(fixedstr));
+ if (result > 0) {
+ _kdc_r_log(r, 4, "Requested flags: %s", fixedstr);
+ kdc_audit_addkv((kdc_request_t)r, KDC_AUDIT_EATWHITE,
+ "flags", "%s", fixedstr);
+ }
+ }
+}
+
+/*
+ * verify the flags on `client' and `server', returning 0
+ * if they are OK and generating an error messages and returning
+ * and error code otherwise.
+ */
+
+KDC_LIB_FUNCTION krb5_error_code KDC_LIB_CALL
+kdc_check_flags(astgs_request_t r,
+ krb5_boolean is_as_req,
+ hdb_entry *client,
+ hdb_entry *server)
+{
+ if (client != NULL) {
+ /* check client */
+ if (client->flags.locked_out) {
+ kdc_audit_addreason((kdc_request_t)r, "Client is locked out");
+ return KRB5KDC_ERR_CLIENT_REVOKED;
+ }
+
+ if (client->flags.invalid) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "Client has invalid bit set");
+ return KRB5KDC_ERR_POLICY;
+ }
+
+ if (!client->flags.client) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "Principal may not act as client");
+ return KRB5KDC_ERR_POLICY;
+ }
+
+ if (client->valid_start && *client->valid_start > kdc_time) {
+ char starttime_str[100];
+ krb5_format_time(r->context, *client->valid_start,
+ starttime_str, sizeof(starttime_str), TRUE);
+ kdc_audit_addreason((kdc_request_t)r, "Client not yet valid "
+ "until %s", starttime_str);
+ return KRB5KDC_ERR_CLIENT_NOTYET;
+ }
+
+ if (client->valid_end && *client->valid_end < kdc_time) {
+ char endtime_str[100];
+ krb5_format_time(r->context, *client->valid_end,
+ endtime_str, sizeof(endtime_str), TRUE);
+ kdc_audit_addreason((kdc_request_t)r, "Client expired at %s",
+ endtime_str);
+ return KRB5KDC_ERR_NAME_EXP;
+ }
+
+ if (client->flags.require_pwchange &&
+ (server == NULL || !server->flags.change_pw))
+ return KRB5KDC_ERR_KEY_EXPIRED;
+
+ if (client->pw_end && *client->pw_end < kdc_time
+ && (server == NULL || !server->flags.change_pw)) {
+ char pwend_str[100];
+ krb5_format_time(r->context, *client->pw_end,
+ pwend_str, sizeof(pwend_str), TRUE);
+ kdc_audit_addreason((kdc_request_t)r, "Client's key has expired "
+ "at %s", pwend_str);
+ return KRB5KDC_ERR_KEY_EXPIRED;
+ }
+ }
+
+ /* check server */
+
+ if (server != NULL) {
+ if (server->flags.locked_out) {
+ kdc_audit_addreason((kdc_request_t)r, "Server locked out");
+ return KRB5KDC_ERR_SERVICE_REVOKED;
+ }
+ if (server->flags.invalid) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "Server has invalid flag set");
+ return KRB5KDC_ERR_POLICY;
+ }
+ if (!server->flags.server) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "Principal may not act as server");
+ return KRB5KDC_ERR_POLICY;
+ }
+
+ if (!is_as_req && server->flags.initial) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "AS-REQ is required for server");
+ return KRB5KDC_ERR_POLICY;
+ }
+
+ if (server->valid_start && *server->valid_start > kdc_time) {
+ char starttime_str[100];
+ krb5_format_time(r->context, *server->valid_start,
+ starttime_str, sizeof(starttime_str), TRUE);
+ kdc_audit_addreason((kdc_request_t)r, "Server not yet valid "
+ "until %s", starttime_str);
+ return KRB5KDC_ERR_SERVICE_NOTYET;
+ }
+
+ if (server->valid_end && *server->valid_end < kdc_time) {
+ char endtime_str[100];
+ krb5_format_time(r->context, *server->valid_end,
+ endtime_str, sizeof(endtime_str), TRUE);
+ kdc_audit_addreason((kdc_request_t)r, "Server expired at %s",
+ endtime_str);
+ return KRB5KDC_ERR_SERVICE_EXP;
+ }
+
+ if (server->pw_end && *server->pw_end < kdc_time) {
+ char pwend_str[100];
+ krb5_format_time(r->context, *server->pw_end,
+ pwend_str, sizeof(pwend_str), TRUE);
+ kdc_audit_addreason((kdc_request_t)r, "Server's key has expired "
+ "at %s", pwend_str);
+ return KRB5KDC_ERR_KEY_EXPIRED;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Return TRUE if `from' is part of `addresses' taking into consideration
+ * the configuration variables that tells us how strict we should be about
+ * these checks
+ */
+
+krb5_boolean
+_kdc_check_addresses(astgs_request_t r, HostAddresses *addresses,
+ const struct sockaddr *from)
+{
+ krb5_kdc_configuration *config = r->config;
+ krb5_error_code ret;
+ krb5_address addr;
+ krb5_boolean result;
+ krb5_boolean only_netbios = TRUE;
+ size_t i;
+
+ if (!config->check_ticket_addresses && !config->warn_ticket_addresses)
+ return TRUE;
+
+ /*
+ * Fields of HostAddresses type are always OPTIONAL and should be non-
+ * empty, but we check for empty just in case as our compiler doesn't
+ * support size constraints on SEQUENCE OF.
+ */
+ if (addresses == NULL || addresses->len == 0)
+ return config->allow_null_ticket_addresses;
+
+ for (i = 0; i < addresses->len; ++i) {
+ if (addresses->val[i].addr_type != KRB5_ADDRESS_NETBIOS) {
+ only_netbios = FALSE;
+ }
+ }
+
+ /* Windows sends it's netbios name, which I can only assume is
+ * used for the 'allowed workstations' check. This is painful,
+ * but we still want to check IP addresses if they happen to be
+ * present.
+ */
+
+ if(only_netbios)
+ return config->allow_null_ticket_addresses;
+
+ ret = krb5_sockaddr2address (r->context, from, &addr);
+ if(ret)
+ return FALSE;
+
+ result = krb5_address_search(r->context, &addr, addresses);
+ krb5_free_address (r->context, &addr);
+ return result;
+}
+
+/*
+ *
+ */
+krb5_error_code
+_kdc_check_anon_policy(astgs_request_t r)
+{
+ if (!r->config->allow_anonymous) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "Anonymous tickets denied by local policy");
+ return KRB5KDC_ERR_POLICY;
+ }
+
+ return 0;
+}
+
+/*
+ * Determine whether the client requested a PAC be included
+ * or excluded explictly, or whether it doesn't care.
+ */
+
+static uint64_t
+get_pac_attributes(krb5_context context, KDC_REQ *req)
+{
+ krb5_error_code ret;
+ PA_PAC_REQUEST pacreq;
+ const PA_DATA *pa;
+ int i = 0;
+ uint32_t pac_attributes;
+
+ pa = _kdc_find_padata(req, &i, KRB5_PADATA_PA_PAC_REQUEST);
+ if (pa == NULL)
+ return KRB5_PAC_WAS_GIVEN_IMPLICITLY;
+
+ ret = decode_PA_PAC_REQUEST(pa->padata_value.data,
+ pa->padata_value.length,
+ &pacreq,
+ NULL);
+ if (ret)
+ return KRB5_PAC_WAS_GIVEN_IMPLICITLY;
+
+ pac_attributes = pacreq.include_pac ? KRB5_PAC_WAS_REQUESTED : 0;
+ free_PA_PAC_REQUEST(&pacreq);
+ return pac_attributes;
+}
+
+/*
+ *
+ */
+
+static krb5_error_code
+generate_pac(astgs_request_t r, const Key *skey, const Key *tkey,
+ krb5_boolean is_tgs)
+{
+ krb5_error_code ret;
+ krb5_data data;
+ uint16_t rodc_id;
+ krb5_principal client;
+ krb5_const_principal canon_princ = NULL;
+
+ r->pac_attributes = get_pac_attributes(r->context, &r->req);
+ kdc_audit_setkv_number((kdc_request_t)r, "pac_attributes",
+ r->pac_attributes);
+
+ if (!is_tgs && !(r->pac_attributes & (KRB5_PAC_WAS_REQUESTED | KRB5_PAC_WAS_GIVEN_IMPLICITLY)))
+ return 0;
+
+ /*
+ * When a PA mech does not use the client's long-term key, the PAC
+ * may include the client's long-term key (encrypted in the reply key)
+ * for use by other shared secret authentication protocols, e.g. NTLM.
+ * Validate a PA mech was actually used before doing this.
+ */
+
+ ret = _kdc_pac_generate(r,
+ r->client,
+ r->server,
+ r->pa_used && !pa_used_flag_isset(r, PA_USES_LONG_TERM_KEY)
+ ? &r->reply_key : NULL,
+ r->pac_attributes,
+ &r->pac);
+ if (ret) {
+ _kdc_r_log(r, 4, "PAC generation failed for -- %s",
+ r->cname);
+ return ret;
+ }
+ if (r->pac == NULL)
+ return 0;
+
+ rodc_id = r->server->kvno >> 16;
+
+ /* libkrb5 expects ticket and PAC client names to match */
+ ret = _krb5_principalname2krb5_principal(r->context, &client,
+ r->et.cname, r->et.crealm);
+ if (ret)
+ return ret;
+
+ /*
+ * Include the canonical name of the principal in the authorization
+ * data, if the realms match (if they don't, then the KDC could
+ * impersonate any realm. Windows always canonicalizes the realm,
+ * but Heimdal permits aliases between realms.)
+ */
+ if (krb5_realm_compare(r->context, client, r->canon_client_princ)) {
+ char *cpn = NULL;
+
+ canon_princ = r->canon_client_princ;
+
+ (void) krb5_unparse_name(r->context, canon_princ, &cpn);
+ kdc_audit_addkv((kdc_request_t)r, 0, "canon_client_name", "%s",
+ cpn ? cpn : "<unknown>");
+ krb5_xfree(cpn);
+ }
+
+ if (r->pa_used && r->pa_used->finalize_pac) {
+ ret = r->pa_used->finalize_pac(r);
+ if (ret)
+ return ret;
+ }
+
+ ret = _krb5_pac_sign(r->context,
+ r->pac,
+ r->et.authtime,
+ client,
+ &skey->key, /* Server key */
+ &tkey->key, /* TGS key */
+ rodc_id,
+ NULL, /* UPN */
+ canon_princ,
+ FALSE, /* add_full_sig */
+ is_tgs ? &r->pac_attributes : NULL,
+ &data);
+ krb5_free_principal(r->context, client);
+ krb5_pac_free(r->context, r->pac);
+ r->pac = NULL;
+ if (ret) {
+ _kdc_r_log(r, 4, "PAC signing failed for -- %s",
+ r->cname);
+ return ret;
+ }
+
+ ret = _kdc_tkt_insert_pac(r->context, &r->et, &data);
+ krb5_data_free(&data);
+
+ return ret;
+}
+
+/*
+ *
+ */
+
+krb5_boolean
+_kdc_is_anonymous(krb5_context context, krb5_const_principal principal)
+{
+ return krb5_principal_is_anonymous(context, principal, KRB5_ANON_MATCH_ANY);
+}
+
+/*
+ * Returns TRUE if principal is the unauthenticated anonymous identity,
+ * i.e. WELLKNOWN/ANONYMOUS@WELLKNOWN:ANONYMOUS. Unfortunately due to
+ * backwards compatibility logic in krb5_principal_is_anonymous() we
+ * have to use our own implementation.
+ */
+
+krb5_boolean
+_kdc_is_anonymous_pkinit(krb5_context context, krb5_const_principal principal)
+{
+ return _kdc_is_anonymous(context, principal) &&
+ strcmp(principal->realm, KRB5_ANON_REALM) == 0;
+}
+
+static int
+require_preauth_p(astgs_request_t r)
+{
+ return r->config->require_preauth
+ || r->client->flags.require_preauth
+ || r->server->flags.require_preauth;
+}
+
+
+/*
+ *
+ */
+
+static krb5_error_code
+add_enc_pa_rep(astgs_request_t r)
+{
+ krb5_error_code ret;
+ krb5_crypto crypto;
+ Checksum checksum;
+ krb5_data cdata;
+ size_t len;
+
+ ret = krb5_crypto_init(r->context, &r->reply_key, 0, &crypto);
+ if (ret)
+ return ret;
+
+ ret = krb5_create_checksum(r->context, crypto,
+ KRB5_KU_AS_REQ, 0,
+ r->request.data, r->request.length,
+ &checksum);
+ krb5_crypto_destroy(r->context, crypto);
+ if (ret)
+ return ret;
+
+ ASN1_MALLOC_ENCODE(Checksum, cdata.data, cdata.length,
+ &checksum, &len, ret);
+ free_Checksum(&checksum);
+ if (ret)
+ return ret;
+ heim_assert(cdata.length == len, "ASN.1 internal error");
+
+ if (r->ek.encrypted_pa_data == NULL) {
+ ALLOC(r->ek.encrypted_pa_data);
+ if (r->ek.encrypted_pa_data == NULL)
+ return ENOMEM;
+ }
+ ret = krb5_padata_add(r->context, r->ek.encrypted_pa_data,
+ KRB5_PADATA_REQ_ENC_PA_REP, cdata.data, cdata.length);
+ if (ret)
+ return ret;
+
+ if (!r->config->enable_fast)
+ return 0;
+
+ return krb5_padata_add(r->context, r->ek.encrypted_pa_data,
+ KRB5_PADATA_FX_FAST, NULL, 0);
+}
+
+/*
+ * Add an authorization data element indicating that a synthetic
+ * principal was used, so that the TGS does not accidentally
+ * synthesize a non-synthetic principal that has since been deleted.
+ */
+static krb5_error_code
+add_synthetic_princ_ad(astgs_request_t r)
+{
+ krb5_data data;
+
+ krb5_data_zero(&data);
+
+ return _kdc_tkt_add_if_relevant_ad(r->context, &r->et,
+ KRB5_AUTHDATA_SYNTHETIC_PRINC_USED,
+ &data);
+}
+
+static krb5_error_code
+get_local_tgs(krb5_context context,
+ krb5_kdc_configuration *config,
+ krb5_const_realm realm,
+ HDB **krbtgtdb,
+ hdb_entry **krbtgt)
+{
+ krb5_error_code ret;
+ krb5_principal tgs_name;
+
+ *krbtgtdb = NULL;
+ *krbtgt = NULL;
+
+ ret = krb5_make_principal(context,
+ &tgs_name,
+ realm,
+ KRB5_TGS_NAME,
+ realm,
+ NULL);
+ if (ret == 0)
+ ret = _kdc_db_fetch(context, config, tgs_name,
+ HDB_F_GET_KRBTGT, NULL, krbtgtdb, krbtgt);
+
+ krb5_free_principal(context, tgs_name);
+ return ret;
+}
+
+/*
+ *
+ */
+
+krb5_error_code
+_kdc_as_rep(astgs_request_t r)
+{
+ krb5_kdc_configuration *config = r->config;
+ KDC_REQ *req = &r->req;
+ const char *from = r->from;
+ KDC_REQ_BODY *b = NULL;
+ KDC_REP *rep = &r->rep;
+ KDCOptions f;
+ krb5_enctype setype;
+ krb5_error_code ret = 0;
+ Key *skey;
+ int found_pa = 0;
+ int i, flags = HDB_F_FOR_AS_REQ;
+ const PA_DATA *pa;
+ krb5_boolean is_tgs;
+ const char *msg;
+ Key *krbtgt_key;
+ unsigned krbtgt_kvno;
+
+ memset(rep, 0, sizeof(*rep));
+
+ ALLOC(rep->padata);
+ if (rep->padata == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(r->context, ret, N_("malloc: out of memory", ""));
+ goto out;
+ }
+
+ /*
+ * Look for FAST armor and unwrap
+ */
+ ret = _kdc_fast_unwrap_request(r, NULL, NULL);
+ if (ret) {
+ _kdc_r_log(r, 1, "FAST unwrap request from %s failed: %d", from, ret);
+ goto out;
+ }
+
+ /* Validate armor TGT, and initialize the armor client and PAC */
+ if (r->armor_ticket) {
+ ret = _kdc_fast_check_armor_pac(r, HDB_F_FOR_AS_REQ);
+ if (ret)
+ goto out;
+ }
+
+ b = &req->req_body;
+ f = b->kdc_options;
+
+ if (f.canonicalize)
+ flags |= HDB_F_CANON;
+
+ if (b->sname == NULL) {
+ ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
+ _kdc_set_e_text(r, "No server in request");
+ goto out;
+ }
+
+ ret = _krb5_principalname2krb5_principal(r->context, &r->server_princ,
+ *(b->sname), b->realm);
+ if (!ret)
+ ret = krb5_unparse_name(r->context, r->server_princ, &r->sname);
+ if (ret) {
+ kdc_log(r->context, config, 2,
+ "AS_REQ malformed server name from %s", from);
+ goto out;
+ }
+
+ if (b->cname == NULL) {
+ ret = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
+ _kdc_set_e_text(r, "No client in request");
+ goto out;
+ }
+
+ ret = _krb5_principalname2krb5_principal(r->context, &r->client_princ,
+ *(b->cname), b->realm);
+ if (!ret)
+ ret = krb5_unparse_name(r->context, r->client_princ, &r->cname);
+ if (ret) {
+ kdc_log(r->context, config, 2,
+ "AS-REQ malformed client name from %s", from);
+ goto out;
+ }
+
+ kdc_log(r->context, config, 4, "AS-REQ %s from %s for %s",
+ r->cname, r->from, r->sname);
+
+ is_tgs = krb5_principal_is_krbtgt(r->context, r->server_princ);
+
+ if (_kdc_is_anonymous(r->context, r->client_princ) &&
+ !_kdc_is_anon_request(req)) {
+ kdc_log(r->context, config, 2, "Anonymous client w/o anonymous flag");
+ ret = KRB5KDC_ERR_BADOPTION;
+ goto out;
+ }
+
+ ret = _kdc_db_fetch(r->context, config, r->client_princ,
+ HDB_F_GET_CLIENT | HDB_F_SYNTHETIC_OK | flags, NULL,
+ &r->clientdb, &r->client);
+ switch (ret) {
+ case 0: /* Success */
+ break;
+ case HDB_ERR_NOT_FOUND_HERE:
+ kdc_log(r->context, config, 5, "client %s does not have secrets at this KDC, need to proxy",
+ r->cname);
+ goto out;
+ case HDB_ERR_WRONG_REALM: {
+ char *fixed_client_name = NULL;
+
+ ret = krb5_unparse_name(r->context, r->client->principal,
+ &fixed_client_name);
+ if (ret) {
+ goto out;
+ }
+
+ kdc_log(r->context, config, 4, "WRONG_REALM - %s -> %s",
+ r->cname, fixed_client_name);
+ free(fixed_client_name);
+
+ r->e_text = NULL;
+ ret = _kdc_fast_mk_error(r, r->rep.padata, r->armor_crypto,
+ &req->req_body,
+ r->error_code = KRB5_KDC_ERR_WRONG_REALM,
+ r->client->principal, r->server_princ,
+ NULL, NULL, r->reply);
+ goto out;
+ }
+ default:
+ {
+ msg = krb5_get_error_message(r->context, ret);
+ kdc_log(r->context, config, 4, "UNKNOWN -- %s: %s", r->cname, msg);
+ krb5_free_error_message(r->context, msg);
+ ret = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
+ kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
+ KDC_AUTH_EVENT_CLIENT_UNKNOWN);
+ goto out;
+ }
+ }
+
+ kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
+ KDC_AUTH_EVENT_CLIENT_FOUND);
+
+ ret = _kdc_db_fetch(r->context, config, r->server_princ,
+ HDB_F_GET_SERVER | HDB_F_DELAY_NEW_KEYS |
+ flags | (is_tgs ? HDB_F_GET_KRBTGT : 0),
+ NULL, &r->serverdb, &r->server);
+ switch (ret) {
+ case 0: /* Success */
+ break;
+ case HDB_ERR_NOT_FOUND_HERE:
+ kdc_log(r->context, config, 5, "target %s does not have secrets at this KDC, need to proxy",
+ r->sname);
+ goto out;
+ default:
+ msg = krb5_get_error_message(r->context, ret);
+ kdc_log(r->context, config, 4, "UNKNOWN -- %s: %s", r->sname, msg);
+ krb5_free_error_message(r->context, msg);
+ ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
+ goto out;
+ }
+
+ ret = _kdc_check_access(r);
+ if(ret)
+ goto out;
+
+ /*
+ * This has to be here (not later), because we need to have r->sessionetype
+ * set prior to calling pa_pkinit_validate(), which in turn calls
+ * _kdc_pk_mk_pa_reply(), during padata validation.
+ */
+
+ /*
+ * Select an enctype for the to-be-issued ticket's session key using the
+ * intersection of the client's requested enctypes and the server's (like a
+ * root krbtgt, but not necessarily) etypes from its HDB entry.
+ */
+ ret = _kdc_find_session_etype(r, b->etype.val, b->etype.len,
+ r->server, &r->sessionetype);
+ if (ret) {
+ kdc_log(r->context, config, 4,
+ "Client (%s) from %s has no common enctypes with KDC "
+ "to use for the session key",
+ r->cname, from);
+ goto out;
+ }
+
+ /*
+ * Select the best encryption type for the KDC without regard to
+ * the client since the client never needs to read that data.
+ */
+
+ ret = _kdc_get_preferred_key(r->context, config,
+ r->server, r->sname,
+ &setype, &skey);
+ if(ret)
+ goto out;
+
+ /* If server is not krbtgt, fetch local krbtgt key for signing authdata */
+ if (is_tgs) {
+ krbtgt_key = skey;
+ krbtgt_kvno = r->server->kvno;
+ } else {
+ ret = get_local_tgs(r->context, config, r->server_princ->realm,
+ &r->krbtgtdb, &r->krbtgt);
+ if (ret)
+ goto out;
+
+ ret = _kdc_get_preferred_key(r->context, config, r->krbtgt,
+ r->server_princ->realm,
+ NULL, &krbtgt_key);
+ if (ret)
+ goto out;
+
+ krbtgt_kvno = r->server->kvno;
+ }
+
+ /*
+ * Pre-auth processing
+ */
+
+ if(req->padata){
+ unsigned int n;
+
+ log_patypes(r, req->padata);
+
+ /* Check if preauth matching */
+
+ for (n = 0; !found_pa && n < sizeof(pat) / sizeof(pat[0]); n++) {
+ if (pat[n].validate == NULL)
+ continue;
+ if (r->armor_crypto == NULL && (pat[n].flags & PA_REQ_FAST))
+ continue;
+ if (!r->config->enable_fast_cookie && (pat[n].flags & PA_USES_FAST_COOKIE))
+ continue;
+
+ kdc_log(r->context, config, 5,
+ "Looking for %s pa-data -- %s", pat[n].name, r->cname);
+ i = 0;
+ pa = _kdc_find_padata(req, &i, pat[n].type);
+ if (pa) {
+ if (r->client->flags.synthetic &&
+ !(pat[n].flags & PA_SYNTHETIC_OK)) {
+ kdc_log(r->context, config, 4, "UNKNOWN -- %s", r->cname);
+ ret = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
+ goto out;
+ }
+ kdc_audit_addkv((kdc_request_t)r, KDC_AUDIT_VIS, "pa", "%s",
+ pat[n].name);
+ ret = pat[n].validate(r, pa);
+ if (ret != 0) {
+ krb5_error_code ret2;
+ Key *ckey = NULL;
+ krb5_boolean default_salt;
+
+ if (ret != KRB5_KDC_ERR_MORE_PREAUTH_DATA_REQUIRED &&
+ !kdc_audit_getkv((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT))
+ kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
+ KDC_AUTH_EVENT_PREAUTH_FAILED);
+
+ /*
+ * If there is a client key, send ETYPE_INFO{,2}
+ */
+ if (!r->client->flags.locked_out) {
+ ret2 = _kdc_find_etype(r, KFE_IS_PREAUTH|KFE_USE_CLIENT,
+ b->etype.val, b->etype.len,
+ NULL, &ckey, &default_salt);
+ if (ret2 == 0) {
+ ret2 = get_pa_etype_info_both(r->context, config, &b->etype,
+ r->rep.padata, ckey, !default_salt);
+ if (ret2 != 0)
+ ret = ret2;
+ }
+ }
+ goto out;
+ }
+ if (!kdc_audit_getkv((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT))
+ kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
+ KDC_AUTH_EVENT_PREAUTH_SUCCEEDED);
+ kdc_log(r->context, config, 4,
+ "%s pre-authentication succeeded -- %s",
+ pat[n].name, r->cname);
+ found_pa = 1;
+ r->pa_used = &pat[n];
+ r->et.flags.pre_authent = 1;
+ }
+ }
+ }
+
+ if (found_pa == 0) {
+ Key *ckey = NULL;
+ size_t n;
+ krb5_boolean default_salt;
+
+ if (r->client->flags.synthetic) {
+ kdc_log(r->context, config, 4, "UNKNOWN -- %s", r->cname);
+ ret = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
+ goto out;
+ }
+
+ for (n = 0; n < sizeof(pat) / sizeof(pat[0]); n++) {
+ if ((pat[n].flags & PA_ANNOUNCE) == 0)
+ continue;
+
+ if (!r->armor_crypto && (pat[n].flags & PA_REQ_FAST))
+ continue;
+ if (pat[n].type == KRB5_PADATA_PKINIT_KX && !r->config->allow_anonymous)
+ continue;
+ if (pat[n].type == KRB5_PADATA_ENC_TIMESTAMP) {
+ if (r->armor_crypto && !r->config->enable_armored_pa_enc_timestamp)
+ continue;
+ if (!r->armor_crypto && !r->config->enable_unarmored_pa_enc_timestamp)
+ continue;
+ }
+ if (pat[n].type == KRB5_PADATA_FX_FAST && !r->config->enable_fast)
+ continue;
+ if (pat[n].type == KRB5_PADATA_GSS && !r->config->enable_gss_preauth)
+ continue;
+ if (!r->config->enable_fast_cookie && (pat[n].flags & PA_USES_FAST_COOKIE))
+ continue;
+
+ ret = krb5_padata_add(r->context, r->rep.padata,
+ pat[n].type, NULL, 0);
+ if (ret)
+ goto out;
+ }
+
+ /*
+ * If there is a client key, send ETYPE_INFO{,2}
+ */
+ ret = _kdc_find_etype(r, KFE_IS_PREAUTH|KFE_USE_CLIENT,
+ b->etype.val, b->etype.len,
+ NULL, &ckey, &default_salt);
+ if (ret == 0) {
+ ret = get_pa_etype_info_both(r->context, config, &b->etype,
+ r->rep.padata, ckey, !default_salt);
+ if (ret)
+ goto out;
+ }
+
+ /*
+ * If the client indicated support for PKINIT Freshness, send back a
+ * freshness token.
+ */
+ ret = send_freshness_token(r, krbtgt_key, krbtgt_kvno);
+ if (ret)
+ goto out;
+
+ /*
+ * send requre preauth is its required or anon is requested,
+ * anon is today only allowed via preauth mechanisms.
+ */
+ if (require_preauth_p(r) || _kdc_is_anon_request(&r->req)) {
+ ret = KRB5KDC_ERR_PREAUTH_REQUIRED;
+ _kdc_set_e_text(r, "Need to use PA-ENC-TIMESTAMP/PA-PK-AS-REQ");
+ goto out;
+ }
+
+ if (ckey == NULL) {
+ ret = KRB5KDC_ERR_CLIENT_NOTYET;
+ _kdc_set_e_text(r, "Doesn't have a client key available");
+ goto out;
+ }
+ krb5_free_keyblock_contents(r->context, &r->reply_key);
+ ret = krb5_copy_keyblock_contents(r->context, &ckey->key, &r->reply_key);
+ if (ret)
+ goto out;
+ }
+
+ r->canon_client_princ = r->client->principal;
+
+ if (_kdc_is_anon_request(&r->req)) {
+ ret = _kdc_check_anon_policy(r);
+ if (ret) {
+ _kdc_set_e_text(r, "Anonymous ticket requests are disabled");
+ goto out;
+ }
+
+ r->et.flags.anonymous = 1;
+ }
+
+ kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
+ KDC_AUTH_EVENT_CLIENT_AUTHORIZED);
+
+ if(f.renew || f.validate || f.proxy || f.forwarded || f.enc_tkt_in_skey) {
+ ret = KRB5KDC_ERR_BADOPTION;
+ _kdc_set_e_text(r, "Bad KDC options");
+ goto out;
+ }
+
+ /*
+ * Build reply
+ */
+ rep->pvno = 5;
+ rep->msg_type = krb_as_rep;
+
+ if (!config->historical_anon_realm &&
+ _kdc_is_anonymous(r->context, r->client_princ)) {
+ Realm anon_realm = KRB5_ANON_REALM;
+ ret = copy_Realm(&anon_realm, &rep->crealm);
+ } else if (f.canonicalize || r->client->flags.force_canonicalize)
+ ret = copy_Realm(&r->canon_client_princ->realm, &rep->crealm);
+ else
+ ret = copy_Realm(&r->client_princ->realm, &rep->crealm);
+ if (ret)
+ goto out;
+ if (r->et.flags.anonymous)
+ ret = _kdc_make_anonymous_principalname(&rep->cname);
+ else if (f.canonicalize || r->client->flags.force_canonicalize)
+ ret = _krb5_principal2principalname(&rep->cname, r->canon_client_princ);
+ else
+ ret = _krb5_principal2principalname(&rep->cname, r->client_princ);
+ if (ret)
+ goto out;
+
+ rep->ticket.tkt_vno = 5;
+ if (f.canonicalize || r->server->flags.force_canonicalize)
+ ret = copy_Realm(&r->server->principal->realm, &rep->ticket.realm);
+ else
+ ret = copy_Realm(&r->server_princ->realm, &rep->ticket.realm);
+ if (ret)
+ goto out;
+ if (f.canonicalize || r->server->flags.force_canonicalize)
+ _krb5_principal2principalname(&rep->ticket.sname,
+ r->server->principal);
+ else
+ _krb5_principal2principalname(&rep->ticket.sname,
+ r->server_princ);
+ /* java 1.6 expects the name to be the same type, lets allow that
+ * uncomplicated name-types, when f.canonicalize is not set (to
+ * match Windows Server 1709). */
+#define CNT(sp,t) (((sp)->sname->name_type) == KRB5_NT_##t)
+ if (!f.canonicalize
+ && (CNT(b, UNKNOWN) || CNT(b, PRINCIPAL) || CNT(b, SRV_INST) || CNT(b, SRV_HST) || CNT(b, SRV_XHST))) {
+ rep->ticket.sname.name_type = b->sname->name_type;
+ }
+#undef CNT
+
+ r->et.flags.initial = 1;
+ if(r->client->flags.forwardable && r->server->flags.forwardable)
+ r->et.flags.forwardable = f.forwardable;
+ if(r->client->flags.proxiable && r->server->flags.proxiable)
+ r->et.flags.proxiable = f.proxiable;
+ else if (f.proxiable) {
+ _kdc_set_e_text(r, "Ticket may not be proxiable");
+ ret = KRB5KDC_ERR_POLICY;
+ goto out;
+ }
+ if(r->client->flags.postdate && r->server->flags.postdate)
+ r->et.flags.may_postdate = f.allow_postdate;
+ else if (f.allow_postdate){
+ _kdc_set_e_text(r, "Ticket may not be postdateable");
+ ret = KRB5KDC_ERR_POLICY;
+ goto out;
+ }
+
+ if (b->addresses)
+ kdc_audit_addaddrs((kdc_request_t)r, b->addresses, "reqaddrs");
+
+ /* check for valid set of addresses */
+ if (!_kdc_check_addresses(r, b->addresses, r->addr)) {
+ if (r->config->warn_ticket_addresses) {
+ kdc_audit_setkv_bool((kdc_request_t)r, "wrongaddr", TRUE);
+ } else {
+ _kdc_set_e_text(r, "Request from wrong address");
+ ret = KRB5KRB_AP_ERR_BADADDR;
+ goto out;
+ }
+ }
+
+ ret = copy_PrincipalName(&rep->cname, &r->et.cname);
+ if (ret)
+ goto out;
+ ret = copy_Realm(&rep->crealm, &r->et.crealm);
+ if (ret)
+ goto out;
+
+ {
+ time_t start;
+ time_t t;
+
+ start = r->et.authtime = kdc_time;
+
+ if(f.postdated && req->req_body.from){
+ ALLOC(r->et.starttime);
+ start = *r->et.starttime = *req->req_body.from;
+ r->et.flags.invalid = 1;
+ r->et.flags.postdated = 1; /* XXX ??? */
+ }
+ _kdc_fix_time(&b->till);
+ t = *b->till;
+
+ /* be careful not to overflow */
+
+ /*
+ * Pre-auth can override r->client->max_life if configured.
+ *
+ * See pre-auth methods, specifically PKINIT, which can get or derive
+ * this from the client's certificate.
+ */
+ if (r->pa_max_life > 0)
+ t = rk_time_add(start, min(rk_time_sub(t, start), r->pa_max_life));
+ else if (r->client->max_life)
+ t = rk_time_add(start, min(rk_time_sub(t, start),
+ *r->client->max_life));
+
+ if (r->server->max_life)
+ t = rk_time_add(start, min(rk_time_sub(t, start),
+ *r->server->max_life));
+
+ /* Pre-auth can bound endtime as well */
+ if (r->pa_endtime > 0)
+ t = rk_time_add(start, min(rk_time_sub(t, start), r->pa_endtime));
+#if 0
+ t = min(t, rk_time_add(start, realm->max_life));
+#endif
+ r->et.endtime = t;
+
+ if (start > r->et.endtime) {
+ _kdc_set_e_text(r, "Requested effective lifetime is negative or too short");
+ ret = KRB5KDC_ERR_NEVER_VALID;
+ goto out;
+ }
+
+ if(f.renewable_ok && r->et.endtime < *b->till){
+ f.renewable = 1;
+ if(b->rtime == NULL){
+ ALLOC(b->rtime);
+ *b->rtime = 0;
+ }
+ if(*b->rtime < *b->till)
+ *b->rtime = *b->till;
+ }
+ if(f.renewable && b->rtime){
+ t = *b->rtime;
+ if(t == 0)
+ t = MAX_TIME;
+ if(r->client->max_renew)
+ t = rk_time_add(start, min(rk_time_sub(t, start),
+ *r->client->max_renew));
+ if(r->server->max_renew)
+ t = rk_time_add(start, min(rk_time_sub(t, start),
+ *r->server->max_renew));
+#if 0
+ t = min(t, rk_time_add(start, realm->max_renew));
+#endif
+ ALLOC(r->et.renew_till);
+ *r->et.renew_till = t;
+ r->et.flags.renewable = 1;
+ }
+ }
+
+ if(b->addresses){
+ ALLOC(r->et.caddr);
+ copy_HostAddresses(b->addresses, r->et.caddr);
+ }
+
+ r->et.transited.tr_type = domain_X500_Compress;
+ krb5_data_zero(&r->et.transited.contents);
+
+ /* The MIT ASN.1 library (obviously) doesn't tell lengths encoded
+ * as 0 and as 0x80 (meaning indefinite length) apart, and is thus
+ * incapable of correctly decoding SEQUENCE OF's of zero length.
+ *
+ * To fix this, always send at least one no-op last_req
+ *
+ * If there's a pw_end or valid_end we will use that,
+ * otherwise just a dummy lr.
+ */
+ r->ek.last_req.val = malloc(2 * sizeof(*r->ek.last_req.val));
+ if (r->ek.last_req.val == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+ r->ek.last_req.len = 0;
+ if (r->client->pw_end
+ && (config->kdc_warn_pwexpire == 0
+ || kdc_time + config->kdc_warn_pwexpire >= *r->client->pw_end)) {
+ r->ek.last_req.val[r->ek.last_req.len].lr_type = LR_PW_EXPTIME;
+ r->ek.last_req.val[r->ek.last_req.len].lr_value = *r->client->pw_end;
+ ++r->ek.last_req.len;
+ }
+ if (r->client->valid_end) {
+ r->ek.last_req.val[r->ek.last_req.len].lr_type = LR_ACCT_EXPTIME;
+ r->ek.last_req.val[r->ek.last_req.len].lr_value = *r->client->valid_end;
+ ++r->ek.last_req.len;
+ }
+ if (r->ek.last_req.len == 0) {
+ r->ek.last_req.val[r->ek.last_req.len].lr_type = LR_NONE;
+ r->ek.last_req.val[r->ek.last_req.len].lr_value = 0;
+ ++r->ek.last_req.len;
+ }
+ /* Set the nonce if it’s not already set. */
+ if (!r->ek.nonce) {
+ r->ek.nonce = b->nonce;
+ }
+ if (r->client->valid_end || r->client->pw_end) {
+ ALLOC(r->ek.key_expiration);
+ if (r->client->valid_end) {
+ if (r->client->pw_end)
+ *r->ek.key_expiration = min(*r->client->valid_end,
+ *r->client->pw_end);
+ else
+ *r->ek.key_expiration = *r->client->valid_end;
+ } else
+ *r->ek.key_expiration = *r->client->pw_end;
+ } else
+ r->ek.key_expiration = NULL;
+ r->ek.flags = r->et.flags;
+ r->ek.authtime = r->et.authtime;
+ if (r->et.starttime) {
+ ALLOC(r->ek.starttime);
+ *r->ek.starttime = *r->et.starttime;
+ }
+ r->ek.endtime = r->et.endtime;
+ if (r->et.renew_till) {
+ ALLOC(r->ek.renew_till);
+ *r->ek.renew_till = *r->et.renew_till;
+ }
+ ret = copy_Realm(&rep->ticket.realm, &r->ek.srealm);
+ if (ret)
+ goto out;
+ ret = copy_PrincipalName(&rep->ticket.sname, &r->ek.sname);
+ if (ret)
+ goto out;
+ if(r->et.caddr){
+ ALLOC(r->ek.caddr);
+ copy_HostAddresses(r->et.caddr, r->ek.caddr);
+ }
+
+ /*
+ * Check session and reply keys
+ */
+
+ if (r->session_key.keytype == ETYPE_NULL) {
+ ret = krb5_generate_random_keyblock(r->context, r->sessionetype, &r->session_key);
+ if (ret)
+ goto out;
+ }
+
+ if (r->reply_key.keytype == ETYPE_NULL) {
+ _kdc_set_e_text(r, "Client has no reply key");
+ ret = KRB5KDC_ERR_CLIENT_NOTYET;
+ goto out;
+ }
+
+ ret = copy_EncryptionKey(&r->session_key, &r->et.key);
+ if (ret)
+ goto out;
+
+ ret = copy_EncryptionKey(&r->session_key, &r->ek.key);
+ if (ret)
+ goto out;
+
+ /* Add the PAC */
+ if (!r->et.flags.anonymous) {
+ ret = generate_pac(r, skey, krbtgt_key, is_tgs);
+ if (ret)
+ goto out;
+ }
+
+ if (r->client->flags.synthetic) {
+ ret = add_synthetic_princ_ad(r);
+ if (ret)
+ goto out;
+ }
+
+ _kdc_log_timestamp(r, "AS-REQ", r->et.authtime,
+ r->et.starttime, r->et.endtime,
+ r->et.renew_till);
+
+ _log_astgs_req(r, setype);
+
+ /*
+ * We always say we support FAST/enc-pa-rep
+ */
+
+ r->et.flags.enc_pa_rep = r->ek.flags.enc_pa_rep = 1;
+
+ /*
+ * update reply-key with strengthen-key
+ */
+
+ ret = _kdc_fast_strengthen_reply_key(r);
+ if (ret)
+ goto out;
+
+ /*
+ * Add REQ_ENC_PA_REP if client supports it
+ */
+
+ i = 0;
+ pa = _kdc_find_padata(req, &i, KRB5_PADATA_REQ_ENC_PA_REP);
+ if (pa) {
+
+ ret = add_enc_pa_rep(r);
+ if (ret) {
+ msg = krb5_get_error_message(r->context, ret);
+ _kdc_r_log(r, 4, "add_enc_pa_rep failed: %s: %d", msg, ret);
+ krb5_free_error_message(r->context, msg);
+ goto out;
+ }
+ }
+
+ /*
+ * Last chance for plugins to update reply
+ */
+ ret = _kdc_finalize_reply(r);
+ if (ret)
+ goto out;
+
+ /*
+ * Don't send kvno from client entry if the pre-authentication
+ * mechanism replaced the reply key.
+ */
+
+ ret = _kdc_encode_reply(r->context, config,
+ r, req->req_body.nonce, setype,
+ r->server->kvno, &skey->key,
+ pa_used_flag_isset(r, PA_REPLACE_REPLY_KEY) ? 0 : r->client->kvno,
+ 0, r->reply);
+ if (ret)
+ goto out;
+
+ /*
+ * Check if message is too large
+ */
+ if (r->datagram_reply && r->reply->length > config->max_datagram_reply_length) {
+ krb5_data_free(r->reply);
+ ret = KRB5KRB_ERR_RESPONSE_TOO_BIG;
+ _kdc_set_e_text(r, "Reply packet too large");
+ }
+
+out:
+ if (ret) {
+ /* Overwrite ‘error_code’ only if we have an actual error. */
+ r->error_code = ret;
+ }
+ {
+ krb5_error_code ret2 = _kdc_audit_request(r);
+ if (ret2) {
+ krb5_data_free(r->reply);
+ ret = ret2;
+ }
+ }
+
+ /*
+ * In case of a non proxy error, build an error message.
+ */
+ if (ret != 0 && ret != HDB_ERR_NOT_FOUND_HERE && r->reply->length == 0) {
+ kdc_log(r->context, config, 5, "as-req: sending error: %d to client", ret);
+ ret = _kdc_fast_mk_error(r,
+ r->rep.padata,
+ r->armor_crypto,
+ &req->req_body,
+ r->error_code ? r->error_code : ret,
+ r->client_princ,
+ r->server_princ,
+ NULL, NULL,
+ r->reply);
+ }
+
+ if (r->pa_used && r->pa_used->cleanup)
+ r->pa_used->cleanup(r);
+
+ free_AS_REP(&r->rep);
+ free_EncTicketPart(&r->et);
+ free_EncKDCRepPart(&r->ek);
+ _kdc_free_fast_state(&r->fast);
+
+ if (r->client_princ) {
+ krb5_free_principal(r->context, r->client_princ);
+ r->client_princ = NULL;
+ }
+ if (r->server_princ){
+ krb5_free_principal(r->context, r->server_princ);
+ r->server_princ = NULL;
+ }
+ if (r->client)
+ _kdc_free_ent(r->context, r->clientdb, r->client);
+ if (r->server)
+ _kdc_free_ent(r->context, r->serverdb, r->server);
+ if (r->krbtgt)
+ _kdc_free_ent(r->context, r->krbtgtdb, r->krbtgt);
+ if (r->armor_crypto) {
+ krb5_crypto_destroy(r->context, r->armor_crypto);
+ r->armor_crypto = NULL;
+ }
+ if (r->armor_ticket)
+ krb5_free_ticket(r->context, r->armor_ticket);
+ if (r->armor_server)
+ _kdc_free_ent(r->context, r->armor_serverdb, r->armor_server);
+ krb5_free_keyblock_contents(r->context, &r->reply_key);
+ krb5_free_keyblock_contents(r->context, &r->enc_ad_key);
+ krb5_free_keyblock_contents(r->context, &r->session_key);
+ krb5_free_keyblock_contents(r->context, &r->strengthen_key);
+ krb5_pac_free(r->context, r->pac);
+
+ return ret;
+}
diff --git a/third_party/heimdal/kdc/krb5tgs.c b/third_party/heimdal/kdc/krb5tgs.c
new file mode 100644
index 0000000..d744f56
--- /dev/null
+++ b/third_party/heimdal/kdc/krb5tgs.c
@@ -0,0 +1,2321 @@
+/*
+ * Copyright (c) 1997-2008 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "kdc_locl.h"
+
+/*
+ * return the realm of a krbtgt-ticket or NULL
+ */
+
+static Realm
+get_krbtgt_realm(const PrincipalName *p)
+{
+ if(p->name_string.len == 2
+ && strcmp(p->name_string.val[0], KRB5_TGS_NAME) == 0)
+ return p->name_string.val[1];
+ else
+ return NULL;
+}
+
+/*
+ * return TRUE if client was a synthetic principal, as indicated by
+ * authorization data
+ */
+krb5_boolean
+_kdc_synthetic_princ_used_p(krb5_context context, krb5_ticket *ticket)
+{
+ krb5_data synthetic_princ_used;
+ krb5_error_code ret;
+
+ ret = krb5_ticket_get_authorization_data_type(context, ticket,
+ KRB5_AUTHDATA_SYNTHETIC_PRINC_USED,
+ &synthetic_princ_used);
+ if (ret == ENOENT)
+ ret = krb5_ticket_get_authorization_data_type(context, ticket,
+ KRB5_AUTHDATA_INITIAL_VERIFIED_CAS,
+ &synthetic_princ_used);
+
+ if (ret == 0)
+ krb5_data_free(&synthetic_princ_used);
+
+ return ret == 0;
+}
+
+/*
+ *
+ */
+
+krb5_error_code
+_kdc_check_pac(astgs_request_t r,
+ const krb5_principal client_principal,
+ hdb_entry *delegated_proxy,
+ hdb_entry *client,
+ hdb_entry *server,
+ hdb_entry *krbtgt,
+ hdb_entry *ticket_server,
+ const EncryptionKey *server_check_key,
+ const EncryptionKey *krbtgt_check_key,
+ EncTicketPart *tkt,
+ krb5_boolean *kdc_issued,
+ krb5_pac *ppac,
+ krb5_principal *pac_canon_name,
+ uint64_t *pac_attributes)
+{
+ krb5_context context = r->context;
+ krb5_kdc_configuration *config = r->config;
+ krb5_pac pac = NULL;
+ krb5_error_code ret;
+ krb5_boolean signedticket;
+
+ *kdc_issued = FALSE;
+ *ppac = NULL;
+ if (pac_canon_name)
+ *pac_canon_name = NULL;
+ if (pac_attributes)
+ *pac_attributes = KRB5_PAC_WAS_GIVEN_IMPLICITLY;
+
+ ret = _krb5_kdc_pac_ticket_parse(context, tkt, &signedticket, &pac);
+ if (ret)
+ return ret;
+
+ if (pac == NULL) {
+ if (config->require_pac)
+ ret = KRB5KDC_ERR_TGT_REVOKED;
+ return ret;
+ }
+
+ /* Verify the server signature. */
+ ret = krb5_pac_verify(context, pac, tkt->authtime, client_principal,
+ server_check_key, NULL);
+ if (ret) {
+ krb5_pac_free(context, pac);
+ return ret;
+ }
+
+ /* Verify the KDC signatures. */
+ ret = _kdc_pac_verify(r,
+ client_principal, delegated_proxy,
+ client, server, krbtgt, tkt, pac);
+ if (ret == 0) {
+ if (pac_canon_name) {
+ ret = _krb5_pac_get_canon_principal(context, pac, pac_canon_name);
+ if (ret && ret != ENOENT) {
+ krb5_pac_free(context, pac);
+ return ret;
+ }
+ }
+ if (pac_attributes &&
+ _krb5_pac_get_attributes_info(context, pac, pac_attributes) != 0)
+ *pac_attributes = KRB5_PAC_WAS_GIVEN_IMPLICITLY;
+ } else if (ret == KRB5_PLUGIN_NO_HANDLE) {
+ /*
+ * We can't verify the KDC signatures if the ticket was issued by
+ * another realm's KDC.
+ */
+ if (krb5_realm_compare(context, server->principal,
+ ticket_server->principal)) {
+ ret = krb5_pac_verify(context, pac, 0, NULL, NULL,
+ krbtgt_check_key);
+ if (ret) {
+ krb5_pac_free(context, pac);
+ return ret;
+ }
+ }
+
+ if (pac_canon_name) {
+ ret = _krb5_pac_get_canon_principal(context, pac, pac_canon_name);
+ if (ret && ret != ENOENT) {
+ krb5_pac_free(context, pac);
+ return ret;
+ }
+ }
+ if (pac_attributes &&
+ _krb5_pac_get_attributes_info(context, pac, pac_attributes) != 0)
+ *pac_attributes = KRB5_PAC_WAS_GIVEN_IMPLICITLY;
+
+ /* Discard the PAC if the plugin didn't handle it */
+ krb5_pac_free(context, pac);
+ ret = krb5_pac_init(context, &pac);
+ if (ret)
+ return ret;
+ } else {
+ krb5_pac_free(context, pac);
+ return ret;
+ }
+
+ *kdc_issued = signedticket ||
+ krb5_principal_is_krbtgt(context,
+ ticket_server->principal);
+ *ppac = pac;
+
+ return 0;
+}
+
+static krb5_boolean
+is_anon_tgs_request_p(const KDC_REQ_BODY *b,
+ const EncTicketPart *tgt)
+{
+ KDCOptions f = b->kdc_options;
+
+ /*
+ * Versions of Heimdal from 1.0 to 7.6, inclusive, send both the
+ * request-anonymous and cname-in-addl-tkt flags for constrained
+ * delegation requests. A true anonymous TGS request will only
+ * have the request-anonymous flag set. (A corollary of this is
+ * that it is not possible to support anonymous constrained
+ * delegation requests, although they would be of limited utility.)
+ */
+ return tgt->flags.anonymous ||
+ (f.request_anonymous && !f.cname_in_addl_tkt && !b->additional_tickets);
+}
+
+/*
+ *
+ */
+
+static krb5_error_code
+check_tgs_flags(astgs_request_t r, KDC_REQ_BODY *b,
+ krb5_const_principal tgt_name,
+ const EncTicketPart *tgt, EncTicketPart *et)
+{
+ KDCOptions f = b->kdc_options;
+
+ if(f.validate){
+ if (!tgt->flags.invalid || tgt->starttime == NULL) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "Bad request to validate ticket");
+ return KRB5KDC_ERR_BADOPTION;
+ }
+ if(*tgt->starttime > kdc_time){
+ kdc_audit_addreason((kdc_request_t)r,
+ "Early request to validate ticket");
+ return KRB5KRB_AP_ERR_TKT_NYV;
+ }
+ /* XXX tkt = tgt */
+ et->flags.invalid = 0;
+ } else if (tgt->flags.invalid) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "Ticket-granting ticket has INVALID flag set");
+ return KRB5KRB_AP_ERR_TKT_INVALID;
+ }
+
+ if(f.forwardable){
+ if (!tgt->flags.forwardable) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "Bad request for forwardable ticket");
+ return KRB5KDC_ERR_BADOPTION;
+ }
+ et->flags.forwardable = 1;
+ }
+ if(f.forwarded){
+ if (!tgt->flags.forwardable) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "Request to forward non-forwardable ticket");
+ return KRB5KDC_ERR_BADOPTION;
+ }
+ et->flags.forwarded = 1;
+ et->caddr = b->addresses;
+ }
+ if(tgt->flags.forwarded)
+ et->flags.forwarded = 1;
+
+ if(f.proxiable){
+ if (!tgt->flags.proxiable) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "Bad request for proxiable ticket");
+ return KRB5KDC_ERR_BADOPTION;
+ }
+ et->flags.proxiable = 1;
+ }
+ if(f.proxy){
+ if (!tgt->flags.proxiable) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "Request to proxy non-proxiable ticket");
+ return KRB5KDC_ERR_BADOPTION;
+ }
+ et->flags.proxy = 1;
+ et->caddr = b->addresses;
+ }
+ if(tgt->flags.proxy)
+ et->flags.proxy = 1;
+
+ if(f.allow_postdate){
+ if (!tgt->flags.may_postdate) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "Bad request for post-datable ticket");
+ return KRB5KDC_ERR_BADOPTION;
+ }
+ et->flags.may_postdate = 1;
+ }
+ if(f.postdated){
+ if (!tgt->flags.may_postdate) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "Bad request for postdated ticket");
+ return KRB5KDC_ERR_BADOPTION;
+ }
+ if(b->from)
+ *et->starttime = *b->from;
+ et->flags.postdated = 1;
+ et->flags.invalid = 1;
+ } else if (b->from && *b->from > kdc_time + r->context->max_skew) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "Ticket cannot be postdated");
+ return KRB5KDC_ERR_CANNOT_POSTDATE;
+ }
+
+ if(f.renewable){
+ if (!tgt->flags.renewable || tgt->renew_till == NULL) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "Bad request for renewable ticket");
+ return KRB5KDC_ERR_BADOPTION;
+ }
+ et->flags.renewable = 1;
+ ALLOC(et->renew_till);
+ _kdc_fix_time(&b->rtime);
+ *et->renew_till = *b->rtime;
+ }
+ if(f.renew){
+ time_t old_life;
+ if (!tgt->flags.renewable || tgt->renew_till == NULL) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "Request to renew non-renewable ticket");
+ return KRB5KDC_ERR_BADOPTION;
+ }
+ old_life = tgt->endtime;
+ if(tgt->starttime)
+ old_life -= *tgt->starttime;
+ else
+ old_life -= tgt->authtime;
+ et->endtime = *et->starttime + old_life;
+ if (et->renew_till != NULL)
+ et->endtime = min(*et->renew_till, et->endtime);
+ }
+
+ /*
+ * RFC 8062 section 3 defines an anonymous ticket as one containing
+ * the anonymous principal and the anonymous ticket flag.
+ */
+ if (tgt->flags.anonymous &&
+ !_kdc_is_anonymous(r->context, tgt_name)) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "Anonymous ticket flag set without "
+ "anonymous principal");
+ return KRB5KDC_ERR_BADOPTION;
+ }
+
+ /*
+ * RFC 8062 section 4.2 states that if the TGT is anonymous, the
+ * anonymous KDC option SHOULD be set, but it is not required.
+ * Treat an anonymous TGT as if the anonymous flag was set.
+ */
+ if (is_anon_tgs_request_p(b, tgt))
+ et->flags.anonymous = 1;
+
+ return 0;
+}
+
+/*
+ * Determine if s4u2self is allowed from this client to this server
+ *
+ * also:
+ *
+ * Check that the client (user2user TGT, enc-tkt-in-skey) hosts the
+ * service given by the client.
+ *
+ * For example, regardless of the principal being impersonated, if the
+ * 'client' and 'server' (target) are the same, or server is an SPN
+ * alias of client, then it's safe.
+ */
+
+krb5_error_code
+_kdc_check_client_matches_target_service(krb5_context context,
+ krb5_kdc_configuration *config,
+ HDB *clientdb,
+ hdb_entry *client,
+ hdb_entry *target_server,
+ krb5_const_principal target_server_principal)
+{
+ krb5_error_code ret;
+
+ /*
+ * Always allow the plugin to check, this might be faster, allow a
+ * policy or audit check and can look into the DB records
+ * directly
+ */
+ if (clientdb->hdb_check_client_matches_target_service) {
+ ret = clientdb->hdb_check_client_matches_target_service(context,
+ clientdb,
+ client,
+ target_server);
+ if (ret == 0)
+ return 0;
+ } else if (krb5_principal_compare(context,
+ client->principal,
+ target_server_principal) == TRUE) {
+ /* if client does a s4u2self to itself, and there is no plugin, that is ok */
+ return 0;
+ } else {
+ ret = KRB5KDC_ERR_BADOPTION;
+ }
+ return ret;
+}
+
+/*
+ *
+ */
+
+krb5_error_code
+_kdc_verify_flags(krb5_context context,
+ krb5_kdc_configuration *config,
+ const EncTicketPart *et,
+ const char *pstr)
+{
+ if(et->endtime < kdc_time){
+ kdc_log(context, config, 4, "Ticket expired (%s)", pstr);
+ return KRB5KRB_AP_ERR_TKT_EXPIRED;
+ }
+ if(et->flags.invalid){
+ kdc_log(context, config, 4, "Ticket not valid (%s)", pstr);
+ return KRB5KRB_AP_ERR_TKT_NYV;
+ }
+ return 0;
+}
+
+/*
+ *
+ */
+
+static krb5_error_code
+fix_transited_encoding(krb5_context context,
+ krb5_kdc_configuration *config,
+ krb5_boolean check_policy,
+ const TransitedEncoding *tr,
+ EncTicketPart *et,
+ const char *client_realm,
+ const char *server_realm,
+ const char *tgt_realm)
+{
+ krb5_error_code ret = 0;
+ char **realms, **tmp;
+ unsigned int num_realms;
+ size_t i;
+
+ switch (tr->tr_type) {
+ case domain_X500_Compress:
+ break;
+ case 0:
+ /*
+ * Allow empty content of type 0 because that is was Microsoft
+ * generates in their TGT.
+ */
+ if (tr->contents.length == 0)
+ break;
+ kdc_log(context, config, 4,
+ "Transited type 0 with non empty content");
+ return KRB5KDC_ERR_TRTYPE_NOSUPP;
+ default:
+ kdc_log(context, config, 4,
+ "Unknown transited type: %u", tr->tr_type);
+ return KRB5KDC_ERR_TRTYPE_NOSUPP;
+ }
+
+ ret = krb5_domain_x500_decode(context,
+ tr->contents,
+ &realms,
+ &num_realms,
+ client_realm,
+ server_realm);
+ if(ret){
+ krb5_warn(context, ret,
+ "Decoding transited encoding");
+ return ret;
+ }
+
+ /*
+ * If the realm of the presented tgt is neither the client nor the server
+ * realm, it is a transit realm and must be added to transited set.
+ */
+ if (strcmp(client_realm, tgt_realm) != 0 &&
+ strcmp(server_realm, tgt_realm) != 0) {
+ if (num_realms + 1 > UINT_MAX/sizeof(*realms)) {
+ ret = ERANGE;
+ goto free_realms;
+ }
+ tmp = realloc(realms, (num_realms + 1) * sizeof(*realms));
+ if(tmp == NULL){
+ ret = ENOMEM;
+ goto free_realms;
+ }
+ realms = tmp;
+ realms[num_realms] = strdup(tgt_realm);
+ if(realms[num_realms] == NULL){
+ ret = ENOMEM;
+ goto free_realms;
+ }
+ num_realms++;
+ }
+ if(num_realms == 0) {
+ if (strcmp(client_realm, server_realm) != 0)
+ kdc_log(context, config, 4,
+ "cross-realm %s -> %s", client_realm, server_realm);
+ } else {
+ size_t l = 0;
+ char *rs;
+ for(i = 0; i < num_realms; i++)
+ l += strlen(realms[i]) + 2;
+ rs = malloc(l);
+ if(rs != NULL) {
+ *rs = '\0';
+ for(i = 0; i < num_realms; i++) {
+ if(i > 0)
+ strlcat(rs, ", ", l);
+ strlcat(rs, realms[i], l);
+ }
+ kdc_log(context, config, 4,
+ "cross-realm %s -> %s via [%s]",
+ client_realm, server_realm, rs);
+ free(rs);
+ }
+ }
+ if(check_policy) {
+ ret = krb5_check_transited(context, client_realm,
+ server_realm,
+ realms, num_realms, NULL);
+ if(ret) {
+ krb5_warn(context, ret, "cross-realm %s -> %s",
+ client_realm, server_realm);
+ goto free_realms;
+ }
+ et->flags.transited_policy_checked = 1;
+ }
+ et->transited.tr_type = domain_X500_Compress;
+ ret = krb5_domain_x500_encode(realms, num_realms, &et->transited.contents);
+ if(ret)
+ krb5_warn(context, ret, "Encoding transited encoding");
+ free_realms:
+ for(i = 0; i < num_realms; i++)
+ free(realms[i]);
+ free(realms);
+ return ret;
+}
+
+
+static krb5_error_code
+tgs_make_reply(astgs_request_t r,
+ const EncTicketPart *tgt,
+ const EncryptionKey *serverkey,
+ const EncryptionKey *krbtgtkey,
+ const krb5_keyblock *sessionkey,
+ krb5_kvno kvno,
+ AuthorizationData *auth_data,
+ const char *tgt_realm,
+ uint16_t rodc_id,
+ krb5_boolean add_ticket_sig)
+{
+ KDC_REQ_BODY *b = &r->req.req_body;
+ krb5_data *reply = r->reply;
+ KDC_REP *rep = &r->rep;
+ EncTicketPart *et = &r->et;
+ EncKDCRepPart *ek = &r->ek;
+ KDCOptions f = b->kdc_options;
+ krb5_error_code ret;
+ int is_weak = 0;
+
+ heim_assert(r->client_princ != NULL, "invalid client name passed to tgs_make_reply");
+
+ rep->pvno = 5;
+ rep->msg_type = krb_tgs_rep;
+
+ if (et->authtime == 0)
+ et->authtime = tgt->authtime;
+ _kdc_fix_time(&b->till);
+ et->endtime = min(tgt->endtime, *b->till);
+ ALLOC(et->starttime);
+ *et->starttime = kdc_time;
+
+ ret = check_tgs_flags(r, b, r->client_princ, tgt, et);
+ if(ret)
+ goto out;
+
+ /* We should check the transited encoding if:
+ 1) the request doesn't ask not to be checked
+ 2) globally enforcing a check
+ 3) principal requires checking
+ 4) we allow non-check per-principal, but principal isn't marked as allowing this
+ 5) we don't globally allow this
+ */
+
+#define GLOBAL_FORCE_TRANSITED_CHECK \
+ (r->config->trpolicy == TRPOLICY_ALWAYS_CHECK)
+#define GLOBAL_ALLOW_PER_PRINCIPAL \
+ (r->config->trpolicy == TRPOLICY_ALLOW_PER_PRINCIPAL)
+#define GLOBAL_ALLOW_DISABLE_TRANSITED_CHECK \
+ (r->config->trpolicy == TRPOLICY_ALWAYS_HONOUR_REQUEST)
+
+/* these will consult the database in future release */
+#define PRINCIPAL_FORCE_TRANSITED_CHECK(P) 0
+#define PRINCIPAL_ALLOW_DISABLE_TRANSITED_CHECK(P) 0
+
+ ret = fix_transited_encoding(r->context, r->config,
+ !f.disable_transited_check ||
+ GLOBAL_FORCE_TRANSITED_CHECK ||
+ PRINCIPAL_FORCE_TRANSITED_CHECK(r->server) ||
+ !((GLOBAL_ALLOW_PER_PRINCIPAL &&
+ PRINCIPAL_ALLOW_DISABLE_TRANSITED_CHECK(r->server)) ||
+ GLOBAL_ALLOW_DISABLE_TRANSITED_CHECK),
+ &tgt->transited, et,
+ krb5_principal_get_realm(r->context, r->client_princ),
+ krb5_principal_get_realm(r->context, r->server->principal),
+ tgt_realm);
+
+ {
+ /*
+ * RFC 6806 notes that names MUST NOT be changed in the response to a
+ * TGS request. Hence we ignore the setting of the canonicalize KDC
+ * option. However, for legacy interoperability we do allow the backend
+ * to override this by setting the force-canonicalize HDB flag in the
+ * server entry.
+ */
+ krb5_const_principal rsp;
+
+ if (r->server->flags.force_canonicalize)
+ rsp = r->server->principal;
+ else
+ rsp = r->server_princ;
+ if (ret == 0)
+ ret = copy_Realm(&rsp->realm, &rep->ticket.realm);
+ if (ret == 0)
+ ret = _krb5_principal2principalname(&rep->ticket.sname, rsp);
+ }
+
+ if (ret == 0)
+ ret = copy_Realm(&r->client_princ->realm, &rep->crealm);
+ if (ret)
+ goto out;
+
+ /*
+ * RFC 8062 states "if the ticket in the TGS request is an anonymous
+ * one, the client and client realm are copied from that ticket". So
+ * whilst the TGT flag check below is superfluous, it is included in
+ * order to follow the specification to its letter.
+ */
+ if (et->flags.anonymous && !tgt->flags.anonymous)
+ _kdc_make_anonymous_principalname(&rep->cname);
+ else
+ ret = copy_PrincipalName(&r->client_princ->name, &rep->cname);
+ if (ret)
+ goto out;
+ rep->ticket.tkt_vno = 5;
+
+ ek->caddr = et->caddr;
+
+ {
+ time_t life;
+ life = et->endtime - *et->starttime;
+ if(r->client && r->client->max_life)
+ life = min(life, *r->client->max_life);
+ if(r->server->max_life)
+ life = min(life, *r->server->max_life);
+ et->endtime = *et->starttime + life;
+ }
+ if(f.renewable_ok && tgt->flags.renewable &&
+ et->renew_till == NULL && et->endtime < *b->till &&
+ tgt->renew_till != NULL)
+ {
+ et->flags.renewable = 1;
+ ALLOC(et->renew_till);
+ *et->renew_till = *b->till;
+ }
+ if(et->renew_till){
+ time_t renew;
+ renew = *et->renew_till - *et->starttime;
+ if(r->client && r->client->max_renew)
+ renew = min(renew, *r->client->max_renew);
+ if(r->server->max_renew)
+ renew = min(renew, *r->server->max_renew);
+ *et->renew_till = *et->starttime + renew;
+ }
+
+ if(et->renew_till){
+ *et->renew_till = min(*et->renew_till, *tgt->renew_till);
+ *et->starttime = min(*et->starttime, *et->renew_till);
+ et->endtime = min(et->endtime, *et->renew_till);
+ }
+
+ *et->starttime = min(*et->starttime, et->endtime);
+
+ if(*et->starttime == et->endtime){
+ ret = KRB5KDC_ERR_NEVER_VALID;
+ goto out;
+ }
+ if(et->renew_till && et->endtime == *et->renew_till){
+ free(et->renew_till);
+ et->renew_till = NULL;
+ et->flags.renewable = 0;
+ }
+
+ et->flags.pre_authent = tgt->flags.pre_authent;
+ et->flags.hw_authent = tgt->flags.hw_authent;
+ et->flags.ok_as_delegate = r->server->flags.ok_as_delegate;
+
+ /* See MS-KILE 3.3.5.1 */
+ if (!r->server->flags.forwardable)
+ et->flags.forwardable = 0;
+ if (!r->server->flags.proxiable)
+ et->flags.proxiable = 0;
+
+ if (auth_data) {
+ unsigned int i = 0;
+
+ /* XXX check authdata */
+
+ if (et->authorization_data == NULL) {
+ et->authorization_data = calloc(1, sizeof(*et->authorization_data));
+ if (et->authorization_data == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(r->context, ret, "malloc: out of memory");
+ goto out;
+ }
+ }
+ for(i = 0; i < auth_data->len ; i++) {
+ ret = add_AuthorizationData(et->authorization_data, &auth_data->val[i]);
+ if (ret) {
+ krb5_set_error_message(r->context, ret, "malloc: out of memory");
+ goto out;
+ }
+ }
+ }
+
+ ret = krb5_copy_keyblock_contents(r->context, sessionkey, &et->key);
+ if (ret)
+ goto out;
+ et->crealm = rep->crealm;
+ et->cname = rep->cname;
+
+ ek->key = et->key;
+ /* MIT must have at least one last_req */
+ ek->last_req.val = calloc(1, sizeof(*ek->last_req.val));
+ if (ek->last_req.val == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+ ek->last_req.len = 1; /* set after alloc to avoid null deref on cleanup */
+ ek->nonce = b->nonce;
+ ek->flags = et->flags;
+ ek->authtime = et->authtime;
+ ek->starttime = et->starttime;
+ ek->endtime = et->endtime;
+ ek->renew_till = et->renew_till;
+ ek->srealm = rep->ticket.realm;
+ ek->sname = rep->ticket.sname;
+
+ _kdc_log_timestamp(r, "TGS-REQ", et->authtime, et->starttime,
+ et->endtime, et->renew_till);
+
+ if (krb5_enctype_valid(r->context, serverkey->keytype) != 0
+ && _kdc_is_weak_exception(r->server->principal, serverkey->keytype))
+ {
+ krb5_enctype_enable(r->context, serverkey->keytype);
+ is_weak = 1;
+ }
+
+ if (r->canon_client_princ) {
+ char *cpn;
+
+ (void) krb5_unparse_name(r->context, r->canon_client_princ, &cpn);
+ kdc_audit_addkv((kdc_request_t)r, 0, "canon_client_name", "%s",
+ cpn ? cpn : "<unknown>");
+ krb5_xfree(cpn);
+ }
+
+ /*
+ * For anonymous tickets, we should filter out positive authorization data
+ * that could reveal the client's identity, and return a policy error for
+ * restrictive authorization data. Policy for unknown authorization types
+ * is implementation dependent.
+ */
+ if (r->pac && !et->flags.anonymous) {
+ kdc_audit_setkv_number((kdc_request_t)r, "pac_attributes",
+ r->pac_attributes);
+
+ /*
+ * PACs are included when issuing TGTs, if there is no PAC_ATTRIBUTES
+ * buffer (legacy behavior) or if the attributes buffer indicates the
+ * AS client requested one.
+ */
+ if (_kdc_include_pac_p(r)) {
+ krb5_boolean is_tgs =
+ krb5_principal_is_krbtgt(r->context, r->server->principal);
+
+ ret = _krb5_kdc_pac_sign_ticket(r->context, r->pac, r->client_princ, serverkey,
+ krbtgtkey, rodc_id, NULL, r->canon_client_princ,
+ add_ticket_sig, add_ticket_sig, et,
+ is_tgs ? &r->pac_attributes : NULL);
+ if (ret)
+ goto out;
+ }
+ }
+
+ ret = _kdc_finalize_reply(r);
+ if (ret)
+ goto out;
+
+ /* It is somewhat unclear where the etype in the following
+ encryption should come from. What we have is a session
+ key in the passed tgt, and a list of preferred etypes
+ *for the new ticket*. Should we pick the best possible
+ etype, given the keytype in the tgt, or should we look
+ at the etype list here as well? What if the tgt
+ session key is DES3 and we want a ticket with a (say)
+ CAST session key. Should the DES3 etype be added to the
+ etype list, even if we don't want a session key with
+ DES3? */
+ ret = _kdc_encode_reply(r->context, r->config, r, b->nonce,
+ serverkey->keytype, kvno,
+ serverkey, 0, r->rk_is_subkey, reply);
+ if (is_weak)
+ krb5_enctype_disable(r->context, serverkey->keytype);
+
+ _log_astgs_req(r, serverkey->keytype);
+
+out:
+ return ret;
+}
+
+static krb5_error_code
+tgs_check_authenticator(krb5_context context,
+ krb5_kdc_configuration *config,
+ krb5_auth_context ac,
+ KDC_REQ_BODY *b,
+ krb5_keyblock *key)
+{
+ krb5_authenticator auth;
+ krb5_error_code ret;
+ krb5_crypto crypto;
+
+ ret = krb5_auth_con_getauthenticator(context, ac, &auth);
+ if (ret) {
+ kdc_log(context, config, 2,
+ "Out of memory checking PA-TGS Authenticator");
+ goto out;
+ }
+ if(auth->cksum == NULL){
+ kdc_log(context, config, 4, "No authenticator in request");
+ ret = KRB5KRB_AP_ERR_INAPP_CKSUM;
+ goto out;
+ }
+
+ if (!krb5_checksum_is_collision_proof(context, auth->cksum->cksumtype)) {
+ kdc_log(context, config, 4, "Bad checksum type in authenticator: %d",
+ auth->cksum->cksumtype);
+ ret = KRB5KRB_AP_ERR_INAPP_CKSUM;
+ goto out;
+ }
+
+ ret = krb5_crypto_init(context, key, 0, &crypto);
+ if (ret) {
+ const char *msg = krb5_get_error_message(context, ret);
+ kdc_log(context, config, 4, "krb5_crypto_init failed: %s", msg);
+ krb5_free_error_message(context, msg);
+ goto out;
+ }
+
+ /*
+ * RFC4120 says the checksum must be collision-proof, but it does
+ * not require it to be keyed (as the authenticator is encrypted).
+ */
+ _krb5_crypto_set_flags(context, crypto, KRB5_CRYPTO_FLAG_ALLOW_UNKEYED_CHECKSUM);
+ ret = _kdc_verify_checksum(context,
+ crypto,
+ KRB5_KU_TGS_REQ_AUTH_CKSUM,
+ &b->_save,
+ auth->cksum);
+ krb5_crypto_destroy(context, crypto);
+ if(ret){
+ const char *msg = krb5_get_error_message(context, ret);
+ kdc_log(context, config, 4,
+ "Failed to verify authenticator checksum: %s", msg);
+ krb5_free_error_message(context, msg);
+ }
+out:
+ free_Authenticator(auth);
+ free(auth);
+ return ret;
+}
+
+static krb5_boolean
+need_referral(krb5_context context, krb5_kdc_configuration *config,
+ const KDCOptions * const options, krb5_principal server,
+ krb5_realm **realms)
+{
+ const char *name;
+
+ if(!options->canonicalize && server->name.name_type != KRB5_NT_SRV_INST)
+ return FALSE;
+
+ if (server->name.name_string.len == 1)
+ name = server->name.name_string.val[0];
+ else if (server->name.name_string.len > 1)
+ name = server->name.name_string.val[1];
+ else
+ return FALSE;
+
+ kdc_log(context, config, 5, "Searching referral for %s", name);
+
+ return _krb5_get_host_realm_int(context, name, FALSE, realms) == 0;
+}
+
+static krb5_error_code
+validate_fast_ad(astgs_request_t r, krb5_authdata *auth_data)
+{
+ krb5_error_code ret;
+ krb5_data data;
+
+ krb5_data_zero(&data);
+
+ if (!r->config->enable_fast)
+ return 0;
+
+ ret = _krb5_get_ad(r->context, auth_data, NULL,
+ KRB5_AUTHDATA_FX_FAST_USED, &data);
+ if (ret == 0) {
+ r->fast_asserted = 1;
+ krb5_data_free(&data);
+ }
+
+ ret = _krb5_get_ad(r->context, auth_data, NULL,
+ KRB5_AUTHDATA_FX_FAST_ARMOR, &data);
+ if (ret == 0) {
+ kdc_log(r->context, r->config, 2,
+ "Invalid ticket usage: TGS-REQ contains AD-fx-fast-armor");
+ krb5_data_free(&data);
+ return KRB5KRB_AP_ERR_BAD_INTEGRITY;
+ }
+
+ return 0;
+}
+
+static krb5_error_code
+tgs_parse_request(astgs_request_t r,
+ const PA_DATA *tgs_req,
+ krb5_enctype *krbtgt_etype,
+ const char *from,
+ const struct sockaddr *from_addr,
+ time_t **csec,
+ int **cusec)
+{
+ krb5_kdc_configuration *config = r->config;
+ KDC_REQ_BODY *b = &r->req.req_body;
+ static char failed[] = "<unparse_name failed>";
+ krb5_ap_req ap_req;
+ krb5_error_code ret;
+ krb5_principal princ;
+ krb5_auth_context ac = NULL;
+ krb5_flags ap_req_options;
+ krb5_flags verify_ap_req_flags = 0;
+ krb5uint32 krbtgt_kvno; /* kvno used for the PA-TGS-REQ AP-REQ Ticket */
+ krb5uint32 krbtgt_kvno_try;
+ int kvno_search_tries = 4; /* number of kvnos to try when tkt_vno == 0 */
+ const Keys *krbtgt_keys;/* keyset for TGT tkt_vno */
+ Key *tkey;
+ krb5_keyblock *subkey = NULL;
+
+ *csec = NULL;
+ *cusec = NULL;
+
+ memset(&ap_req, 0, sizeof(ap_req));
+ ret = krb5_decode_ap_req(r->context, &tgs_req->padata_value, &ap_req);
+ if(ret){
+ const char *msg = krb5_get_error_message(r->context, ret);
+ kdc_log(r->context, config, 4, "Failed to decode AP-REQ: %s", msg);
+ krb5_free_error_message(r->context, msg);
+ goto out;
+ }
+
+ if(!krb5_principalname_is_krbtgt(r->context, &ap_req.ticket.sname)){
+ /*
+ * Note: this check is not to be depended upon for security. Nothing
+ * prevents a client modifying the sname, as it is located in the
+ * unencrypted part of the ticket.
+ */
+
+ /* XXX check for ticket.sname == req.sname */
+ kdc_log(r->context, config, 4, "PA-DATA is not a ticket-granting ticket");
+ ret = KRB5KDC_ERR_POLICY; /* ? */
+ goto out;
+ }
+
+ _krb5_principalname2krb5_principal(r->context,
+ &princ,
+ ap_req.ticket.sname,
+ ap_req.ticket.realm);
+
+ krbtgt_kvno = ap_req.ticket.enc_part.kvno ? *ap_req.ticket.enc_part.kvno : 0;
+ ret = _kdc_db_fetch(r->context, config, princ, HDB_F_GET_KRBTGT,
+ &krbtgt_kvno, &r->krbtgtdb, &r->krbtgt);
+
+ if (ret == HDB_ERR_NOT_FOUND_HERE) {
+ /* XXX Factor out this unparsing of the same princ all over */
+ char *p;
+ ret = krb5_unparse_name(r->context, princ, &p);
+ if (ret != 0)
+ p = failed;
+ krb5_free_principal(r->context, princ);
+ kdc_log(r->context, config, 5,
+ "Ticket-granting ticket account %s does not have secrets at "
+ "this KDC, need to proxy", p);
+ if (ret == 0)
+ free(p);
+ ret = HDB_ERR_NOT_FOUND_HERE;
+ goto out;
+ } else if (ret == HDB_ERR_KVNO_NOT_FOUND) {
+ char *p;
+ ret = krb5_unparse_name(r->context, princ, &p);
+ if (ret != 0)
+ p = failed;
+ krb5_free_principal(r->context, princ);
+ kdc_log(r->context, config, 5,
+ "Ticket-granting ticket account %s does not have keys for "
+ "kvno %d at this KDC", p, krbtgt_kvno);
+ if (ret == 0)
+ free(p);
+ ret = HDB_ERR_KVNO_NOT_FOUND;
+ goto out;
+ } else if (ret == HDB_ERR_NO_MKEY) {
+ char *p;
+ ret = krb5_unparse_name(r->context, princ, &p);
+ if (ret != 0)
+ p = failed;
+ krb5_free_principal(r->context, princ);
+ kdc_log(r->context, config, 5,
+ "Missing master key for decrypting keys for ticket-granting "
+ "ticket account %s with kvno %d at this KDC", p, krbtgt_kvno);
+ if (ret == 0)
+ free(p);
+ ret = HDB_ERR_KVNO_NOT_FOUND;
+ goto out;
+ } else if (ret) {
+ const char *msg = krb5_get_error_message(r->context, ret);
+ char *p;
+ ret = krb5_unparse_name(r->context, princ, &p);
+ if (ret != 0)
+ p = failed;
+ kdc_log(r->context, config, 4,
+ "Ticket-granting ticket %s not found in database: %s", p, msg);
+ krb5_free_principal(r->context, princ);
+ krb5_free_error_message(r->context, msg);
+ if (ret == 0)
+ free(p);
+ ret = KRB5KRB_AP_ERR_NOT_US;
+ goto out;
+ }
+
+ krbtgt_kvno_try = krbtgt_kvno ? krbtgt_kvno : r->krbtgt->kvno;
+ *krbtgt_etype = ap_req.ticket.enc_part.etype;
+
+next_kvno:
+ krbtgt_keys = hdb_kvno2keys(r->context, r->krbtgt, krbtgt_kvno_try);
+ ret = hdb_enctype2key(r->context, r->krbtgt, krbtgt_keys,
+ ap_req.ticket.enc_part.etype, &tkey);
+ if (ret && krbtgt_kvno == 0 && kvno_search_tries > 0) {
+ kvno_search_tries--;
+ krbtgt_kvno_try--;
+ goto next_kvno;
+ } else if (ret) {
+ char *str = NULL, *p = NULL;
+
+ /* We should implement the MIT `trace_format()' concept */
+ (void) krb5_enctype_to_string(r->context, ap_req.ticket.enc_part.etype, &str);
+ (void) krb5_unparse_name(r->context, princ, &p);
+ kdc_log(r->context, config, 4,
+ "No server key with enctype %s found for %s",
+ str ? str : "<unknown enctype>",
+ p ? p : "<unparse_name failed>");
+ free(str);
+ free(p);
+ ret = KRB5KRB_AP_ERR_BADKEYVER;
+ goto out;
+ }
+
+ if (b->kdc_options.validate)
+ verify_ap_req_flags |= KRB5_VERIFY_AP_REQ_IGNORE_INVALID;
+
+ if (r->config->warn_ticket_addresses)
+ verify_ap_req_flags |= KRB5_VERIFY_AP_REQ_IGNORE_ADDRS;
+
+ ret = krb5_verify_ap_req2(r->context,
+ &ac,
+ &ap_req,
+ princ,
+ &tkey->key,
+ verify_ap_req_flags,
+ &ap_req_options,
+ &r->ticket,
+ KRB5_KU_TGS_REQ_AUTH);
+ if (r->ticket && r->ticket->ticket.caddr)
+ kdc_audit_addaddrs((kdc_request_t)r, r->ticket->ticket.caddr, "tixaddrs");
+ if (r->config->warn_ticket_addresses && ret == KRB5KRB_AP_ERR_BADADDR &&
+ r->ticket != NULL) {
+ kdc_audit_setkv_bool((kdc_request_t)r, "wrongaddr", TRUE);
+ ret = 0;
+ }
+ if (ret == KRB5KRB_AP_ERR_BAD_INTEGRITY && kvno_search_tries > 0) {
+ kvno_search_tries--;
+ krbtgt_kvno_try--;
+ goto next_kvno;
+ }
+
+ krb5_free_principal(r->context, princ);
+ if(ret) {
+ const char *msg = krb5_get_error_message(r->context, ret);
+ kdc_log(r->context, config, 4, "Failed to verify AP-REQ: %s", msg);
+ krb5_free_error_message(r->context, msg);
+ goto out;
+ }
+
+ r->ticket_key = tkey;
+
+ {
+ krb5_authenticator auth;
+
+ ret = krb5_auth_con_getauthenticator(r->context, ac, &auth);
+ if (ret == 0) {
+ *csec = malloc(sizeof(**csec));
+ if (*csec == NULL) {
+ krb5_free_authenticator(r->context, &auth);
+ kdc_log(r->context, config, 4, "malloc failed");
+ goto out;
+ }
+ **csec = auth->ctime;
+ *cusec = malloc(sizeof(**cusec));
+ if (*cusec == NULL) {
+ krb5_free_authenticator(r->context, &auth);
+ kdc_log(r->context, config, 4, "malloc failed");
+ goto out;
+ }
+ **cusec = auth->cusec;
+
+ ret = validate_fast_ad(r, auth->authorization_data);
+ krb5_free_authenticator(r->context, &auth);
+ if (ret)
+ goto out;
+ }
+ }
+
+ ret = tgs_check_authenticator(r->context, config, ac, b, &r->ticket->ticket.key);
+ if (ret) {
+ krb5_auth_con_free(r->context, ac);
+ goto out;
+ }
+
+ r->rk_is_subkey = 1;
+
+ ret = krb5_auth_con_getremotesubkey(r->context, ac, &subkey);
+ if(ret){
+ const char *msg = krb5_get_error_message(r->context, ret);
+ krb5_auth_con_free(r->context, ac);
+ kdc_log(r->context, config, 4, "Failed to get remote subkey: %s", msg);
+ krb5_free_error_message(r->context, msg);
+ goto out;
+ }
+ if(subkey == NULL){
+ r->rk_is_subkey = 0;
+
+ ret = krb5_auth_con_getkey(r->context, ac, &subkey);
+ if(ret) {
+ const char *msg = krb5_get_error_message(r->context, ret);
+ krb5_auth_con_free(r->context, ac);
+ kdc_log(r->context, config, 4, "Failed to get session key: %s", msg);
+ krb5_free_error_message(r->context, msg);
+ goto out;
+ }
+ }
+ if(subkey == NULL){
+ krb5_auth_con_free(r->context, ac);
+ kdc_log(r->context, config, 4,
+ "Failed to get key for enc-authorization-data");
+ ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; /* ? */
+ goto out;
+ }
+
+ krb5_free_keyblock_contents(r->context, &r->reply_key);
+ ret = krb5_copy_keyblock_contents(r->context, subkey, &r->reply_key);
+ krb5_free_keyblock(r->context, subkey);
+ if (ret)
+ goto out;
+
+ krb5_free_keyblock_contents(r->context, &r->enc_ad_key);
+ if (b->enc_authorization_data) {
+ ret = krb5_copy_keyblock_contents(r->context,
+ &r->reply_key,
+ &r->enc_ad_key);
+ if (ret)
+ goto out;
+ }
+
+ ret = validate_fast_ad(r, r->ticket->ticket.authorization_data);
+ if (ret)
+ goto out;
+
+
+ /*
+ * Check for FAST request
+ */
+
+ ret = _kdc_fast_unwrap_request(r, r->ticket, ac);
+ if (ret)
+ goto out;
+
+ krb5_auth_con_free(r->context, ac);
+
+out:
+ free_AP_REQ(&ap_req);
+
+ return ret;
+}
+
+static krb5_error_code
+build_server_referral(krb5_context context,
+ krb5_kdc_configuration *config,
+ krb5_crypto session,
+ krb5_const_realm referred_realm,
+ const PrincipalName *true_principal_name,
+ const PrincipalName *requested_principal,
+ krb5_data *outdata)
+{
+ PA_ServerReferralData ref;
+ krb5_error_code ret;
+ EncryptedData ed;
+ krb5_data data;
+ size_t size = 0;
+
+ memset(&ref, 0, sizeof(ref));
+
+ if (referred_realm) {
+ ALLOC(ref.referred_realm);
+ if (ref.referred_realm == NULL)
+ goto eout;
+ *ref.referred_realm = strdup(referred_realm);
+ if (*ref.referred_realm == NULL)
+ goto eout;
+ }
+ if (true_principal_name) {
+ ALLOC(ref.true_principal_name);
+ if (ref.true_principal_name == NULL)
+ goto eout;
+ ret = copy_PrincipalName(true_principal_name, ref.true_principal_name);
+ if (ret)
+ goto eout;
+ }
+ if (requested_principal) {
+ ALLOC(ref.requested_principal_name);
+ if (ref.requested_principal_name == NULL)
+ goto eout;
+ ret = copy_PrincipalName(requested_principal,
+ ref.requested_principal_name);
+ if (ret)
+ goto eout;
+ }
+
+ ASN1_MALLOC_ENCODE(PA_ServerReferralData,
+ data.data, data.length,
+ &ref, &size, ret);
+ free_PA_ServerReferralData(&ref);
+ if (ret)
+ return ret;
+ if (data.length != size)
+ krb5_abortx(context, "internal asn.1 encoder error");
+
+ ret = krb5_encrypt_EncryptedData(context, session,
+ KRB5_KU_PA_SERVER_REFERRAL,
+ data.data, data.length,
+ 0 /* kvno */, &ed);
+ free(data.data);
+ if (ret)
+ return ret;
+
+ ASN1_MALLOC_ENCODE(EncryptedData,
+ outdata->data, outdata->length,
+ &ed, &size, ret);
+ free_EncryptedData(&ed);
+ if (ret)
+ return ret;
+ if (outdata->length != size)
+ krb5_abortx(context, "internal asn.1 encoder error");
+
+ return 0;
+eout:
+ free_PA_ServerReferralData(&ref);
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+}
+
+/*
+ * This function is intended to be used when failure to find the client is
+ * acceptable.
+ */
+krb5_error_code
+_kdc_db_fetch_client(krb5_context context,
+ krb5_kdc_configuration *config,
+ int flags,
+ krb5_principal cp,
+ const char *cpn,
+ const char *krbtgt_realm,
+ HDB **clientdb,
+ hdb_entry **client_out)
+{
+ krb5_error_code ret;
+ hdb_entry *client = NULL;
+
+ *clientdb = NULL;
+ *client_out = NULL;
+
+ ret = _kdc_db_fetch(context, config, cp, HDB_F_GET_CLIENT | flags,
+ NULL, clientdb, &client);
+ if (ret == HDB_ERR_NOT_FOUND_HERE) {
+ /*
+ * This is OK, we are just trying to find out if they have
+ * been disabled or deleted in the meantime; missing secrets
+ * are OK.
+ */
+ } else if (ret) {
+ /*
+ * If the client belongs to the same realm as our TGS, it
+ * should exist in the local database.
+ */
+ const char *msg;
+
+ if (strcmp(krb5_principal_get_realm(context, cp), krbtgt_realm) == 0) {
+ if (ret == HDB_ERR_NOENTRY)
+ ret = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
+ kdc_log(context, config, 4, "Client no longer in database: %s", cpn);
+ return ret;
+ }
+
+ msg = krb5_get_error_message(context, ret);
+ kdc_log(context, config, 4, "Client not found in database: %s", msg);
+ krb5_free_error_message(context, msg);
+ } else if (client->flags.invalid || !client->flags.client) {
+ kdc_log(context, config, 4, "Client has invalid bit set");
+ _kdc_free_ent(context, *clientdb, client);
+ return KRB5KDC_ERR_POLICY;
+ }
+
+ *client_out = client;
+
+ return 0;
+}
+
+static krb5_error_code
+tgs_build_reply(astgs_request_t priv,
+ krb5_enctype krbtgt_etype,
+ const struct sockaddr *from_addr)
+{
+ krb5_context context = priv->context;
+ krb5_kdc_configuration *config = priv->config;
+ KDC_REQ_BODY *b = &priv->req.req_body;
+ const char *from = priv->from;
+ krb5_error_code ret, ret2;
+ krb5_principal krbtgt_out_principal = NULL;
+ krb5_principal user2user_princ = NULL;
+ char *spn = NULL, *cpn = NULL, *krbtgt_out_n = NULL;
+ char *user2user_name = NULL;
+ HDB *user2user_krbtgtdb;
+ hdb_entry *user2user_krbtgt = NULL;
+ HDB *clientdb = NULL;
+ HDB *serverdb = NULL;
+ krb5_realm ref_realm = NULL;
+ EncTicketPart *tgt = &priv->ticket->ticket;
+ const EncryptionKey *ekey;
+ krb5_keyblock sessionkey;
+ krb5_kvno kvno;
+ krb5_pac user2user_pac = NULL;
+ uint16_t rodc_id;
+ krb5_boolean add_ticket_sig = FALSE;
+ const char *tgt_realm = /* Realm of TGT issuer */
+ krb5_principal_get_realm(context, priv->krbtgt->principal);
+ const char *our_realm = /* Realm of this KDC */
+ krb5_principal_get_comp_string(context, priv->krbtgt->principal, 1);
+ char **capath = NULL;
+ size_t num_capath = 0;
+ AuthorizationData *auth_data = NULL;
+
+ HDB *krbtgt_outdb;
+ hdb_entry *krbtgt_out = NULL;
+
+ PrincipalName *s;
+ Realm r;
+ EncTicketPart adtkt;
+ char opt_str[128];
+ krb5_boolean kdc_issued = FALSE;
+
+ Key *tkey_sign;
+ int flags = HDB_F_FOR_TGS_REQ;
+ int server_flags;
+
+ int result;
+
+ const PA_DATA *for_user = NULL;
+ int for_user_idx = 0;
+
+ memset(&sessionkey, 0, sizeof(sessionkey));
+ memset(&adtkt, 0, sizeof(adtkt));
+
+ s = b->sname;
+ r = b->realm;
+
+ /*
+ * The canonicalize KDC option is passed as a hint to the backend, but
+ * can typically be ignored. Per RFC 6806, names are not canonicalized
+ * in response to a TGS request (although we make an exception, see
+ * force-canonicalize below).
+ */
+ if (b->kdc_options.canonicalize)
+ flags |= HDB_F_CANON;
+
+ server_flags = HDB_F_GET_SERVER | HDB_F_DELAY_NEW_KEYS | flags;
+ if (b->kdc_options.enc_tkt_in_skey)
+ server_flags |= HDB_F_USER2USER_PRINCIPAL;
+
+ if (s == NULL) {
+ ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
+ _kdc_set_const_e_text(priv, "No server in request");
+ goto out;
+ }
+
+ _krb5_principalname2krb5_principal(context, &priv->server_princ, *s, r);
+ ret = krb5_unparse_name(context, priv->server_princ, &priv->sname);
+ if (ret)
+ goto out;
+ spn = priv->sname;
+ _krb5_principalname2krb5_principal(context, &priv->client_princ,
+ tgt->cname, tgt->crealm);
+ ret = krb5_unparse_name(context, priv->client_princ, &priv->cname);
+ if (ret)
+ goto out;
+ cpn = priv->cname;
+ result = unparse_flags(KDCOptions2int(b->kdc_options),
+ asn1_KDCOptions_units(),
+ opt_str, sizeof(opt_str));
+ if (result > 0)
+ kdc_log(context, config, 4,
+ "TGS-REQ %s from %s for %s [%s]",
+ cpn, from, spn, opt_str);
+ else
+ kdc_log(context, config, 4,
+ "TGS-REQ %s from %s for %s", cpn, from, spn);
+
+ /*
+ * Fetch server
+ */
+
+server_lookup:
+ if (priv->server)
+ _kdc_free_ent(context, serverdb, priv->server);
+ priv->server = NULL;
+ ret = _kdc_db_fetch(context, config, priv->server_princ,
+ server_flags,
+ NULL, &serverdb, &priv->server);
+ priv->serverdb = serverdb;
+ if (ret == HDB_ERR_NOT_FOUND_HERE) {
+ kdc_log(context, config, 5, "target %s does not have secrets at this KDC, need to proxy", spn);
+ kdc_audit_addreason((kdc_request_t)priv, "Target not found here");
+ goto out;
+ } else if (ret == HDB_ERR_WRONG_REALM) {
+ free(ref_realm);
+ ref_realm = strdup(priv->server->principal->realm);
+ if (ref_realm == NULL) {
+ ret = krb5_enomem(context);
+ goto out;
+ }
+
+ kdc_log(context, config, 4,
+ "Returning a referral to realm %s for "
+ "server %s.",
+ ref_realm, spn);
+ krb5_free_principal(context, priv->server_princ);
+ priv->server_princ = NULL;
+ ret = krb5_make_principal(context, &priv->server_princ, r, KRB5_TGS_NAME,
+ ref_realm, NULL);
+ if (ret)
+ goto out;
+ free(priv->sname);
+ priv->sname = NULL;
+ ret = krb5_unparse_name(context, priv->server_princ, &priv->sname);
+ if (ret)
+ goto out;
+ spn = priv->sname;
+
+ goto server_lookup;
+ } else if (ret) {
+ const char *new_rlm, *msg;
+ Realm req_rlm;
+ krb5_realm *realms;
+
+ priv->error_code = ret; /* advise policy plugin of failure reason */
+ ret2 = _kdc_referral_policy(priv);
+ if (ret2 == 0) {
+ krb5_xfree(priv->sname);
+ priv->sname = NULL;
+ ret = krb5_unparse_name(context, priv->server_princ, &priv->sname);
+ if (ret)
+ goto out;
+ goto server_lookup;
+ } else if (ret2 != KRB5_PLUGIN_NO_HANDLE) {
+ ret = ret2;
+ } else if ((req_rlm = get_krbtgt_realm(&priv->server_princ->name)) != NULL) {
+ if (capath == NULL) {
+ /* With referalls, hierarchical capaths are always enabled */
+ ret2 = _krb5_find_capath(context, tgt->crealm, our_realm,
+ req_rlm, TRUE, &capath, &num_capath);
+ if (ret2) {
+ ret = ret2;
+ kdc_audit_addreason((kdc_request_t)priv,
+ "No trusted path from client realm to ours");
+ goto out;
+ }
+ }
+ new_rlm = num_capath > 0 ? capath[--num_capath] : NULL;
+ if (new_rlm) {
+ kdc_log(context, config, 5, "krbtgt from %s via %s for "
+ "realm %s not found, trying %s", tgt->crealm,
+ our_realm, req_rlm, new_rlm);
+
+ free(ref_realm);
+ ref_realm = strdup(new_rlm);
+ if (ref_realm == NULL) {
+ ret = krb5_enomem(context);
+ goto out;
+ }
+
+ krb5_free_principal(context, priv->server_princ);
+ priv->server_princ = NULL;
+ krb5_make_principal(context, &priv->server_princ, r,
+ KRB5_TGS_NAME, ref_realm, NULL);
+ free(priv->sname);
+ priv->sname = NULL;
+ ret = krb5_unparse_name(context, priv->server_princ, &priv->sname);
+ if (ret)
+ goto out;
+ spn = priv->sname;
+ goto server_lookup;
+ }
+ } else if (need_referral(context, config, &b->kdc_options, priv->server_princ, &realms)) {
+ if (strcmp(realms[0], priv->server_princ->realm) != 0) {
+ kdc_log(context, config, 4,
+ "Returning a referral to realm %s for "
+ "server %s that was not found",
+ realms[0], spn);
+ krb5_free_principal(context, priv->server_princ);
+ priv->server_princ = NULL;
+ krb5_make_principal(context, &priv->server_princ, r, KRB5_TGS_NAME,
+ realms[0], NULL);
+ free(priv->sname);
+ priv->sname = NULL;
+ ret = krb5_unparse_name(context, priv->server_princ, &priv->sname);
+ if (ret) {
+ krb5_free_host_realm(context, realms);
+ goto out;
+ }
+ spn = priv->sname;
+
+ free(ref_realm);
+ ref_realm = strdup(realms[0]);
+
+ krb5_free_host_realm(context, realms);
+ goto server_lookup;
+ }
+ krb5_free_host_realm(context, realms);
+ }
+ msg = krb5_get_error_message(context, ret);
+ kdc_log(context, config, 3,
+ "Server not found in database: %s: %s", spn, msg);
+ krb5_free_error_message(context, msg);
+ if (ret == HDB_ERR_NOENTRY)
+ ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
+ kdc_audit_addreason((kdc_request_t)priv,
+ "Service principal unknown");
+ goto out;
+ }
+
+ /*
+ * Now refetch the primary krbtgt, and get the current kvno (the
+ * sign check may have been on an old kvno, and the server may
+ * have been an incoming trust)
+ */
+
+ ret = krb5_make_principal(context,
+ &krbtgt_out_principal,
+ our_realm,
+ KRB5_TGS_NAME,
+ our_realm,
+ NULL);
+ if (ret) {
+ kdc_log(context, config, 4,
+ "Failed to make krbtgt principal name object for "
+ "authz-data signatures");
+ goto out;
+ }
+ ret = krb5_unparse_name(context, krbtgt_out_principal, &krbtgt_out_n);
+ if (ret) {
+ kdc_log(context, config, 4,
+ "Failed to make krbtgt principal name object for "
+ "authz-data signatures");
+ goto out;
+ }
+
+ ret = _kdc_db_fetch(context, config, krbtgt_out_principal,
+ HDB_F_GET_KRBTGT, NULL, &krbtgt_outdb, &krbtgt_out);
+ if (ret) {
+ char *ktpn = NULL;
+ ret = krb5_unparse_name(context, priv->krbtgt->principal, &ktpn);
+ kdc_log(context, config, 4,
+ "No such principal %s (needed for authz-data signature keys) "
+ "while processing TGS-REQ for service %s with krbtgt %s",
+ krbtgt_out_n, spn, (ret == 0) ? ktpn : "<unknown>");
+ free(ktpn);
+ ret = KRB5KRB_AP_ERR_NOT_US;
+ goto out;
+ }
+
+ /*
+ * Select enctype, return key and kvno.
+ */
+
+ {
+ krb5_enctype etype;
+
+ if(b->kdc_options.enc_tkt_in_skey) {
+ Ticket *t;
+ krb5_principal p;
+ Key *uukey;
+ krb5uint32 second_kvno = 0;
+ krb5uint32 *kvno_ptr = NULL;
+ size_t i;
+ HDB *user2user_db;
+ hdb_entry *user2user_client = NULL;
+ krb5_boolean user2user_kdc_issued = FALSE;
+ char *tpn;
+
+ if(b->additional_tickets == NULL ||
+ b->additional_tickets->len == 0){
+ ret = KRB5KDC_ERR_BADOPTION; /* ? */
+ kdc_log(context, config, 4,
+ "No second ticket present in user-to-user request");
+ kdc_audit_addreason((kdc_request_t)priv,
+ "No second ticket present in user-to-user request");
+ goto out;
+ }
+ t = &b->additional_tickets->val[0];
+ if(!krb5_principalname_is_krbtgt(context, &t->sname)){
+ /*
+ * Note: this check is not to be depended upon for
+ * security. Nothing prevents a client modifying the sname, as
+ * it is located in the unencrypted part of the ticket.
+ */
+
+ kdc_log(context, config, 4,
+ "Additional ticket is not a ticket-granting ticket");
+ kdc_audit_addreason((kdc_request_t)priv,
+ "Additional ticket is not a ticket-granting ticket");
+ ret = KRB5KDC_ERR_POLICY;
+ goto out;
+ }
+ ret = _krb5_principalname2krb5_principal(context, &p, t->sname, t->realm);
+ if (ret)
+ goto out;
+
+ ret = krb5_unparse_name(context, p, &tpn);
+ if (ret)
+ goto out;
+ if(t->enc_part.kvno){
+ second_kvno = *t->enc_part.kvno;
+ kvno_ptr = &second_kvno;
+ }
+ ret = _kdc_db_fetch(context, config, p,
+ HDB_F_GET_KRBTGT, kvno_ptr,
+ &user2user_krbtgtdb, &user2user_krbtgt);
+ krb5_free_principal(context, p);
+ if(ret){
+ if (ret == HDB_ERR_NOENTRY)
+ ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
+ kdc_audit_addreason((kdc_request_t)priv,
+ "User-to-user service principal (TGS) unknown");
+ krb5_xfree(tpn);
+ goto out;
+ }
+ ret = hdb_enctype2key(context, user2user_krbtgt, NULL,
+ t->enc_part.etype, &uukey);
+ if(ret){
+ ret = KRB5KDC_ERR_ETYPE_NOSUPP; /* XXX */
+ kdc_audit_addreason((kdc_request_t)priv,
+ "User-to-user enctype not supported");
+ krb5_xfree(tpn);
+ goto out;
+ }
+ ret = krb5_decrypt_ticket(context, t, &uukey->key, &adtkt, 0);
+ if(ret) {
+ kdc_audit_addreason((kdc_request_t)priv,
+ "User-to-user TGT decrypt failure");
+ krb5_xfree(tpn);
+ goto out;
+ }
+
+ ret = _kdc_verify_flags(context, config, &adtkt, tpn);
+ if (ret) {
+ kdc_audit_addreason((kdc_request_t)priv,
+ "User-to-user TGT expired or invalid");
+ krb5_xfree(tpn);
+ goto out;
+ }
+ krb5_xfree(tpn);
+
+ /* Fetch the name from the TGT. */
+ ret = _krb5_principalname2krb5_principal(context, &user2user_princ,
+ adtkt.cname, adtkt.crealm);
+ if (ret)
+ goto out;
+
+ ret = krb5_unparse_name(context, user2user_princ, &user2user_name);
+ if (ret)
+ goto out;
+
+ /*
+ * Look up the name given in the TGT in the database. The user
+ * claims to have a ticket-granting-ticket to our KDC, so we should
+ * fail hard if we can't find the user - otherwise we can't do
+ * proper checks.
+ */
+ ret = _kdc_db_fetch(context, config, user2user_princ,
+ HDB_F_GET_CLIENT | flags,
+ NULL, &user2user_db, &user2user_client);
+ if (ret == HDB_ERR_NOENTRY)
+ ret = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
+ if (ret)
+ goto out;
+
+ /*
+ * The account is present in the database, now check the
+ * account flags.
+ *
+ * We check this as a client (because the purpose of
+ * user2user is that the server flag is not set, because
+ * the long-term key is not strong, but this does mean
+ * that a client with an expired password can't get accept
+ * a user2user ticket.
+ */
+ ret = kdc_check_flags(priv,
+ FALSE,
+ user2user_client,
+ NULL);
+ if (ret) {
+ _kdc_free_ent(context, user2user_db, user2user_client);
+ goto out;
+ }
+
+ /*
+ * Also check that the account is the same one specified in the
+ * request.
+ */
+ ret = _kdc_check_client_matches_target_service(context,
+ config,
+ serverdb,
+ priv->server,
+ user2user_client,
+ user2user_princ);
+ if (ret) {
+ _kdc_free_ent(context, user2user_db, user2user_client);
+ goto out;
+ }
+
+ /* Verify the PAC of the TGT. */
+ ret = _kdc_check_pac(priv, user2user_princ, NULL,
+ user2user_client, user2user_krbtgt, user2user_krbtgt, user2user_krbtgt,
+ &uukey->key, &priv->ticket_key->key, &adtkt,
+ &user2user_kdc_issued, &user2user_pac, NULL, NULL);
+ _kdc_free_ent(context, user2user_db, user2user_client);
+ if (ret) {
+ const char *msg = krb5_get_error_message(context, ret);
+ kdc_log(context, config, 0,
+ "Verify PAC failed for %s (%s) from %s with %s",
+ spn, user2user_name, from, msg);
+ krb5_free_error_message(context, msg);
+ goto out;
+ }
+
+ if ((config->require_pac && !user2user_pac)
+ || (user2user_pac && !user2user_kdc_issued))
+ {
+ ret = KRB5KDC_ERR_BADOPTION;
+ kdc_log(context, config, 0,
+ "Ticket not signed with PAC; user-to-user failed (%s).",
+ user2user_pac ? "Ticket unsigned" : "No PAC");
+ goto out;
+ }
+
+ ekey = &adtkt.key;
+ for(i = 0; i < b->etype.len; i++)
+ if (b->etype.val[i] == adtkt.key.keytype)
+ break;
+ if(i == b->etype.len) {
+ kdc_log(context, config, 4,
+ "Addition ticket has no matching etypes");
+ krb5_clear_error_message(context);
+ ret = KRB5KDC_ERR_ETYPE_NOSUPP;
+ kdc_audit_addreason((kdc_request_t)priv,
+ "No matching enctypes for 2nd ticket");
+ goto out;
+ }
+ etype = b->etype.val[i];
+ kvno = 0;
+ } else {
+ Key *skey;
+
+ ret = _kdc_find_session_etype(priv, b->etype.val, b->etype.len,
+ priv->server, &etype);
+ if(ret) {
+ kdc_log(context, config, 4,
+ "Server (%s) has no support for etypes", spn);
+ kdc_audit_addreason((kdc_request_t)priv,
+ "Enctype not supported");
+ goto out;
+ }
+ ret = _kdc_get_preferred_key(context, config, priv->server, spn,
+ NULL, &skey);
+ if(ret) {
+ kdc_log(context, config, 4,
+ "Server (%s) has no supported etypes", spn);
+ kdc_audit_addreason((kdc_request_t)priv,
+ "Enctype not supported");
+ goto out;
+ }
+ ekey = &skey->key;
+ kvno = priv->server->kvno;
+ }
+
+ ret = krb5_generate_random_keyblock(context, etype, &sessionkey);
+ if (ret)
+ goto out;
+ }
+
+ /*
+ * Check that service is in the same realm as the krbtgt. If it's
+ * not the same, it's someone that is using a uni-directional trust
+ * backward.
+ */
+
+ /*
+ * The first realm is the realm of the service, the second is
+ * krbtgt/<this>/@REALM component of the krbtgt DN the request was
+ * encrypted to. The redirection via the krbtgt_out entry allows
+ * the DB to possibly correct the case of the realm (Samba4 does
+ * this) before the strcmp()
+ */
+ if (strcmp(krb5_principal_get_realm(context, priv->server->principal),
+ krb5_principal_get_realm(context, krbtgt_out->principal)) != 0) {
+ char *ktpn;
+ ret = krb5_unparse_name(context, krbtgt_out->principal, &ktpn);
+ kdc_log(context, config, 4,
+ "Request with wrong krbtgt: %s",
+ (ret == 0) ? ktpn : "<unknown>");
+ if(ret == 0)
+ free(ktpn);
+ ret = KRB5KRB_AP_ERR_NOT_US;
+ kdc_audit_addreason((kdc_request_t)priv, "Request with wrong TGT");
+ goto out;
+ }
+
+ ret = _kdc_get_preferred_key(context, config, krbtgt_out, krbtgt_out_n,
+ NULL, &tkey_sign);
+ if (ret) {
+ kdc_log(context, config, 4,
+ "Failed to find key for krbtgt PAC signature");
+ kdc_audit_addreason((kdc_request_t)priv,
+ "Failed to find key for krbtgt PAC signature");
+ goto out;
+ }
+ ret = hdb_enctype2key(context, krbtgt_out, NULL,
+ tkey_sign->key.keytype, &tkey_sign);
+ if(ret) {
+ kdc_log(context, config, 4,
+ "Failed to find key for krbtgt PAC signature");
+ kdc_audit_addreason((kdc_request_t)priv,
+ "Failed to find key for krbtgt PAC signature");
+ goto out;
+ }
+
+ if (_kdc_synthetic_princ_used_p(context, priv->ticket))
+ flags |= HDB_F_SYNTHETIC_OK;
+
+ ret = _kdc_db_fetch_client(context, config, flags, priv->client_princ,
+ cpn, our_realm, &clientdb, &priv->client);
+ if (ret)
+ goto out;
+ /* flags &= ~HDB_F_SYNTHETIC_OK; */ /* `flags' is not used again below */
+ priv->clientdb = clientdb;
+
+ /* Validate armor TGT before potentially including device claims */
+ if (priv->armor_ticket) {
+ ret = _kdc_fast_check_armor_pac(priv, HDB_F_FOR_TGS_REQ);
+ if (ret)
+ goto out;
+ }
+
+ ret = _kdc_check_pac(priv, priv->client_princ, NULL,
+ priv->client, priv->server,
+ priv->krbtgt, priv->krbtgt,
+ &priv->ticket_key->key, &priv->ticket_key->key, tgt,
+ &kdc_issued, &priv->pac, &priv->canon_client_princ,
+ &priv->pac_attributes);
+ if (ret) {
+ const char *msg = krb5_get_error_message(context, ret);
+ kdc_audit_addreason((kdc_request_t)priv, "PAC check failed");
+ kdc_log(context, config, 4,
+ "Verify PAC failed for %s (%s) from %s with %s",
+ spn, cpn, from, msg);
+ krb5_free_error_message(context, msg);
+ goto out;
+ }
+
+ /*
+ * Process request
+ */
+
+ /*
+ * Services for User: protocol transition and constrained delegation
+ */
+
+ if (priv->client != NULL &&
+ (for_user = _kdc_find_padata(&priv->req,
+ &for_user_idx,
+ KRB5_PADATA_FOR_USER)) != NULL)
+ {
+ /* Process an S4U2Self request. */
+ ret = _kdc_validate_protocol_transition(priv, for_user);
+ if (ret)
+ goto out;
+ } else if (priv->client != NULL
+ && b->additional_tickets != NULL
+ && b->additional_tickets->len != 0
+ && b->kdc_options.cname_in_addl_tkt
+ && b->kdc_options.enc_tkt_in_skey == 0)
+ {
+ /* Process an S4U2Proxy request. */
+ ret = _kdc_validate_constrained_delegation(priv);
+ if (ret)
+ goto out;
+ } else if (priv->pac != NULL) {
+ ret = _kdc_pac_update(priv, priv->client_princ, NULL, NULL,
+ priv->client, priv->server, priv->krbtgt,
+ &priv->pac);
+ if (ret == KRB5_PLUGIN_NO_HANDLE) {
+ ret = 0;
+ }
+ if (ret) {
+ const char *msg = krb5_get_error_message(context, ret);
+ kdc_audit_addreason((kdc_request_t)priv, "PAC update failed");
+ kdc_log(context, config, 4,
+ "Update PAC failed for %s (%s) from %s with %s",
+ spn, cpn, from, msg);
+ krb5_free_error_message(context, msg);
+ goto out;
+ }
+
+ if (priv->pac == NULL) {
+ /* the plugin may indicate no PAC should be generated */
+ priv->pac_attributes = 0;
+ }
+ }
+
+ if (b->enc_authorization_data) {
+ unsigned auth_data_usage;
+ krb5_crypto crypto;
+ krb5_data ad;
+
+ if (priv->rk_is_subkey != 0) {
+ auth_data_usage = KRB5_KU_TGS_REQ_AUTH_DAT_SUBKEY;
+ } else {
+ auth_data_usage = KRB5_KU_TGS_REQ_AUTH_DAT_SESSION;
+ }
+
+ ret = krb5_crypto_init(context, &priv->enc_ad_key, 0, &crypto);
+ if (ret) {
+ const char *msg = krb5_get_error_message(context, ret);
+ kdc_audit_addreason((kdc_request_t)priv,
+ "krb5_crypto_init() failed for "
+ "enc_authorization_data");
+ kdc_log(context, config, 4, "krb5_crypto_init failed: %s", msg);
+ krb5_free_error_message(context, msg);
+ goto out;
+ }
+ ret = krb5_decrypt_EncryptedData(context,
+ crypto,
+ auth_data_usage,
+ b->enc_authorization_data,
+ &ad);
+ krb5_crypto_destroy(context, crypto);
+ if(ret){
+ kdc_audit_addreason((kdc_request_t)priv,
+ "Failed to decrypt enc-authorization-data");
+ kdc_log(context, config, 4,
+ "Failed to decrypt enc-authorization-data");
+ ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; /* ? */
+ goto out;
+ }
+ ALLOC(auth_data);
+ if (auth_data == NULL) {
+ krb5_data_free(&ad);
+ ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; /* ? */
+ goto out;
+ }
+ ret = decode_AuthorizationData(ad.data, ad.length, auth_data, NULL);
+ krb5_data_free(&ad);
+ if(ret){
+ free(auth_data);
+ auth_data = NULL;
+ kdc_audit_addreason((kdc_request_t)priv,
+ "Failed to decode authorization data");
+ kdc_log(context, config, 4, "Failed to decode authorization data");
+ ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; /* ? */
+ goto out;
+ }
+ }
+
+ /*
+ * Check flags
+ */
+
+ ret = kdc_check_flags(priv, FALSE, priv->client, priv->server);
+ if(ret)
+ goto out;
+
+ if((b->kdc_options.validate || b->kdc_options.renew) &&
+ !krb5_principal_compare(context,
+ priv->krbtgt->principal,
+ priv->server->principal)){
+ kdc_audit_addreason((kdc_request_t)priv, "Inconsistent request");
+ kdc_log(context, config, 4, "Inconsistent request.");
+ ret = KRB5KDC_ERR_SERVER_NOMATCH;
+ goto out;
+ }
+
+ /* check for valid set of addresses */
+ if (!_kdc_check_addresses(priv, tgt->caddr, from_addr)) {
+ if (config->check_ticket_addresses) {
+ ret = KRB5KRB_AP_ERR_BADADDR;
+ kdc_audit_setkv_bool((kdc_request_t)priv, "wrongaddr", TRUE);
+ kdc_log(context, config, 4, "Request from wrong address");
+ kdc_audit_addreason((kdc_request_t)priv, "Request from wrong address");
+ goto out;
+ } else if (config->warn_ticket_addresses) {
+ kdc_audit_setkv_bool((kdc_request_t)priv, "wrongaddr", TRUE);
+ }
+ }
+
+ /* check local and per-principal anonymous ticket issuance policy */
+ if (is_anon_tgs_request_p(b, tgt)) {
+ ret = _kdc_check_anon_policy(priv);
+ if (ret)
+ goto out;
+ }
+
+ /*
+ * If this is an referral, add server referral data to the
+ * auth_data reply .
+ */
+ if (ref_realm) {
+ PA_DATA pa;
+ krb5_crypto crypto;
+
+ kdc_log(context, config, 3,
+ "Adding server referral to %s", ref_realm);
+
+ ret = krb5_crypto_init(context, &sessionkey, 0, &crypto);
+ if (ret)
+ goto out;
+
+ ret = build_server_referral(context, config, crypto, ref_realm,
+ NULL, s, &pa.padata_value);
+ krb5_crypto_destroy(context, crypto);
+ if (ret) {
+ kdc_audit_addreason((kdc_request_t)priv, "Referral build failed");
+ kdc_log(context, config, 4,
+ "Failed building server referral");
+ goto out;
+ }
+ pa.padata_type = KRB5_PADATA_SERVER_REFERRAL;
+
+ ret = add_METHOD_DATA(priv->rep.padata, &pa);
+ krb5_data_free(&pa.padata_value);
+ if (ret) {
+ kdc_log(context, config, 4,
+ "Add server referral METHOD-DATA failed");
+ goto out;
+ }
+ }
+
+ /*
+ * Only add ticket signature if the requested server is not krbtgt, and
+ * either the header server is krbtgt or, in the case of renewal/validation
+ * if it was signed with PAC ticket signature and we verified it.
+ * Currently Heimdal only allows renewal of krbtgt anyway but that might
+ * change one day (see issue #763) so make sure to check for it.
+ */
+
+ if (kdc_issued &&
+ !krb5_principal_is_krbtgt(context, priv->server->principal)) {
+
+ add_ticket_sig = TRUE;
+ }
+
+ /*
+ * Active-Directory implementations use the high part of the kvno as the
+ * read-only-dc identifier, we need to embed it in the PAC KDC signatures.
+ */
+
+ rodc_id = krbtgt_out->kvno >> 16;
+
+ /*
+ *
+ */
+
+ ret = tgs_make_reply(priv,
+ tgt,
+ ekey,
+ &tkey_sign->key,
+ &sessionkey,
+ kvno,
+ auth_data,
+ tgt_realm,
+ rodc_id,
+ add_ticket_sig);
+
+out:
+ free(user2user_name);
+ free(krbtgt_out_n);
+ _krb5_free_capath(context, capath);
+
+ krb5_free_keyblock_contents(context, &sessionkey);
+ if(krbtgt_out)
+ _kdc_free_ent(context, krbtgt_outdb, krbtgt_out);
+ if(user2user_krbtgt)
+ _kdc_free_ent(context, user2user_krbtgtdb, user2user_krbtgt);
+
+ krb5_free_principal(context, user2user_princ);
+ krb5_free_principal(context, krbtgt_out_principal);
+ free(ref_realm);
+
+ if (auth_data) {
+ free_AuthorizationData(auth_data);
+ free(auth_data);
+ }
+
+ free_EncTicketPart(&adtkt);
+
+ krb5_pac_free(context, user2user_pac);
+
+ return ret;
+}
+
+/*
+ *
+ */
+
+krb5_error_code
+_kdc_tgs_rep(astgs_request_t r)
+{
+ krb5_kdc_configuration *config = r->config;
+ KDC_REQ *req = &r->req;
+ krb5_data *data = r->reply;
+ const char *from = r->from;
+ struct sockaddr *from_addr = r->addr;
+ int datagram_reply = r->datagram_reply;
+ krb5_error_code ret;
+ int i = 0;
+ const PA_DATA *tgs_req, *pa;
+ krb5_enctype krbtgt_etype = ETYPE_NULL;
+
+ time_t *csec = NULL;
+ int *cusec = NULL;
+
+ r->e_text = NULL;
+
+ if(req->padata == NULL){
+ ret = KRB5KDC_ERR_PREAUTH_REQUIRED; /* XXX ??? */
+ kdc_log(r->context, config, 4,
+ "TGS-REQ from %s without PA-DATA", from);
+ goto out;
+ }
+
+ i = 0;
+ pa = _kdc_find_padata(&r->req, &i, KRB5_PADATA_FX_FAST_ARMOR);
+ if (pa) {
+ kdc_log(r->context, r->config, 10, "Found TGS-REQ FAST armor inside TGS-REQ pa-data");
+ ret = KRB5KRB_ERR_GENERIC;
+ goto out;
+ }
+
+ i = 0;
+ tgs_req = _kdc_find_padata(req, &i, KRB5_PADATA_TGS_REQ);
+ if(tgs_req == NULL){
+ ret = KRB5KDC_ERR_PADATA_TYPE_NOSUPP;
+
+ kdc_log(r->context, config, 4,
+ "TGS-REQ from %s without PA-TGS-REQ", from);
+ goto out;
+ }
+ ret = tgs_parse_request(r, tgs_req,
+ &krbtgt_etype,
+ from, from_addr,
+ &csec, &cusec);
+ if (ret == HDB_ERR_NOT_FOUND_HERE) {
+ /* kdc_log() is called in tgs_parse_request() */
+ goto out;
+ }
+ if (ret) {
+ kdc_log(r->context, config, 4,
+ "Failed parsing TGS-REQ from %s", from);
+ goto out;
+ }
+
+ ret = _kdc_fast_strengthen_reply_key(r);
+ if (ret)
+ goto out;
+
+ ALLOC(r->rep.padata);
+ if (r->rep.padata == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(r->context, ret, N_("malloc: out of memory", ""));
+ goto out;
+ }
+
+ ret = tgs_build_reply(r,
+ krbtgt_etype,
+ from_addr);
+ if (ret) {
+ kdc_log(r->context, config, 4,
+ "Failed building TGS-REP to %s", from);
+ goto out;
+ }
+
+ /* */
+ if (datagram_reply && data->length > config->max_datagram_reply_length) {
+ krb5_data_free(data);
+ ret = KRB5KRB_ERR_RESPONSE_TOO_BIG;
+ _kdc_set_const_e_text(r, "Reply packet too large");
+ }
+
+out:
+ if (ret) {
+ /* Overwrite ‘error_code’ only if we have an actual error. */
+ r->error_code = ret;
+ }
+ {
+ krb5_error_code ret2 = _kdc_audit_request(r);
+ if (ret2) {
+ krb5_data_free(data);
+ ret = ret2;
+ }
+ }
+
+ if(ret && ret != HDB_ERR_NOT_FOUND_HERE && data->data == NULL){
+ METHOD_DATA error_method = { 0, NULL };
+
+ kdc_log(r->context, config, 5, "tgs-req: sending error: %d to client", ret);
+ ret = _kdc_fast_mk_error(r,
+ &error_method,
+ r->armor_crypto,
+ &req->req_body,
+ r->error_code ? r->error_code : ret,
+ r->client_princ ? r->client_princ :(r->ticket != NULL ? r->ticket->client : NULL),
+ r->server_princ ? r->server_princ :(r->ticket != NULL ? r->ticket->server : NULL),
+ csec, cusec,
+ data);
+ free_METHOD_DATA(&error_method);
+ }
+ free(csec);
+ free(cusec);
+
+ free_TGS_REP(&r->rep);
+ free_TransitedEncoding(&r->et.transited);
+ free(r->et.starttime);
+ free(r->et.renew_till);
+ if(r->et.authorization_data) {
+ free_AuthorizationData(r->et.authorization_data);
+ free(r->et.authorization_data);
+ }
+ free_LastReq(&r->ek.last_req);
+ if (r->et.key.keyvalue.data) {
+ memset_s(r->et.key.keyvalue.data, 0, r->et.key.keyvalue.length,
+ r->et.key.keyvalue.length);
+ }
+ free_EncryptionKey(&r->et.key);
+
+ if (r->canon_client_princ) {
+ krb5_free_principal(r->context, r->canon_client_princ);
+ r->canon_client_princ = NULL;
+ }
+ if (r->armor_crypto) {
+ krb5_crypto_destroy(r->context, r->armor_crypto);
+ r->armor_crypto = NULL;
+ }
+ if (r->armor_ticket)
+ krb5_free_ticket(r->context, r->armor_ticket);
+ if (r->armor_server)
+ _kdc_free_ent(r->context, r->armor_serverdb, r->armor_server);
+ if (r->armor_client)
+ _kdc_free_ent(r->context,
+ r->armor_clientdb,
+ r->armor_client);
+ if (r->armor_pac)
+ krb5_pac_free(r->context, r->armor_pac);
+ krb5_free_keyblock_contents(r->context, &r->reply_key);
+ krb5_free_keyblock_contents(r->context, &r->enc_ad_key);
+ krb5_free_keyblock_contents(r->context, &r->strengthen_key);
+
+ if (r->ticket)
+ krb5_free_ticket(r->context, r->ticket);
+ if (r->krbtgt)
+ _kdc_free_ent(r->context, r->krbtgtdb, r->krbtgt);
+
+ if (r->client)
+ _kdc_free_ent(r->context, r->clientdb, r->client);
+ krb5_free_principal(r->context, r->client_princ);
+ if (r->server)
+ _kdc_free_ent(r->context, r->serverdb, r->server);
+ krb5_free_principal(r->context, r->server_princ);
+ _kdc_free_fast_state(&r->fast);
+ krb5_pac_free(r->context, r->pac);
+
+ return ret;
+}
diff --git a/third_party/heimdal/kdc/kstash-version.rc b/third_party/heimdal/kdc/kstash-version.rc
new file mode 100644
index 0000000..c3d2214
--- /dev/null
+++ b/third_party/heimdal/kdc/kstash-version.rc
@@ -0,0 +1,36 @@
+/***********************************************************************
+ * Copyright (c) 2010, Secure Endpoints Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ **********************************************************************/
+
+#define RC_FILE_TYPE VFT_APP
+#define RC_FILE_DESC_0409 "KDC Master Password Stash Tool"
+#define RC_FILE_ORIG_0409 "kstash.exe"
+
+#include "../windows/version.rc"
diff --git a/third_party/heimdal/kdc/kstash.8 b/third_party/heimdal/kdc/kstash.8
new file mode 100644
index 0000000..615132b
--- /dev/null
+++ b/third_party/heimdal/kdc/kstash.8
@@ -0,0 +1,92 @@
+.\" Copyright (c) 1997 - 2004 Kungliga Tekniska Högskolan
+.\" (Royal Institute of Technology, Stockholm, Sweden).
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\"
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\"
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" 3. Neither the name of the Institute nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $Id$
+.\"
+.Dd April 10, 2007
+.Dt KSTASH 8
+.Os HEIMDAL
+.Sh NAME
+.Nm kstash
+.Nd "store the KDC master password in a file"
+.Sh SYNOPSIS
+.Nm
+.Bk -words
+.Oo Fl e Ar string \*(Ba Xo
+.Fl Fl enctype= Ns Ar string
+.Xc
+.Oc
+.Oo Fl k Ar file \*(Ba Xo
+.Fl Fl key-file= Ns Ar file
+.Xc
+.Oc
+.Op Fl Fl convert-file
+.Op Fl Fl random-key
+.Op Fl Fl master-key-fd= Ns Ar fd
+.Op Fl Fl random-key
+.Op Fl h | Fl Fl help
+.Op Fl Fl version
+.Ek
+.Sh DESCRIPTION
+.Nm
+reads the Kerberos master key and stores it in a file that will be
+used by the KDC.
+.Pp
+Supported options:
+.Bl -tag -width Ds
+.It Fl e Ar string , Fl Fl enctype= Ns Ar string
+the encryption type to use, defaults to DES3-CBC-SHA1.
+.It Fl k Ar file , Fl Fl key-file= Ns Ar file
+the name of the master key file.
+.It Fl Fl convert-file
+don't ask for a new master key, just read an old master key file, and
+write it back in the new keyfile format.
+.It Fl Fl random-key
+generate a random master key.
+.It Fl Fl master-key-fd= Ns Ar fd
+filedescriptor to read passphrase from, if not specified the
+passphrase will be read from the terminal.
+.El
+.\".Sh ENVIRONMENT
+.Sh FILES
+.Pa /var/heimdal/m-key
+is the default keyfile if no other keyfile is specified.
+The format of a Heimdal master key is the same as a keytab, so
+.Nm ktutil
+list can be used to list the content of the file.
+.\".Sh EXAMPLES
+.\".Sh DIAGNOSTICS
+.Sh SEE ALSO
+.Xr kdc 8
+.\".Sh STANDARDS
+.\".Sh HISTORY
+.\".Sh AUTHORS
+.\".Sh BUGS
diff --git a/third_party/heimdal/kdc/kstash.c b/third_party/heimdal/kdc/kstash.c
new file mode 100644
index 0000000..6ec1a54
--- /dev/null
+++ b/third_party/heimdal/kdc/kstash.c
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 1997-2004 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "headers.h"
+
+krb5_context context;
+
+static char *keyfile;
+static int convert_flag;
+static int help_flag;
+static int version_flag;
+
+static int master_key_fd = -1;
+static int random_key_flag;
+
+static const char *enctype_str = "des3-cbc-sha1";
+
+static struct getargs args[] = {
+ { "enctype", 'e', arg_string, rk_UNCONST(&enctype_str), "encryption type",
+ NULL },
+ { "key-file", 'k', arg_string, &keyfile, "master key file", "file" },
+ { "convert-file", 0, arg_flag, &convert_flag,
+ "just convert keyfile to new format", NULL },
+ { "master-key-fd", 0, arg_integer, &master_key_fd,
+ "filedescriptor to read passphrase from", "fd" },
+ { "random-key", 0, arg_flag, &random_key_flag,
+ "generate a random master key", NULL },
+ { "help", 'h', arg_flag, &help_flag, NULL, NULL },
+ { "version", 0, arg_flag, &version_flag, NULL, NULL }
+};
+
+int num_args = sizeof(args) / sizeof(args[0]);
+
+int
+main(int argc, char **argv)
+{
+ char buf[1024+1];
+ krb5_error_code ret;
+ int aret;
+
+ krb5_enctype enctype;
+
+ hdb_master_key mkey;
+
+ krb5_program_setup(&context, argc, argv, args, num_args, NULL);
+
+ if(help_flag)
+ krb5_std_usage(0, args, num_args);
+ if(version_flag){
+ print_version(NULL);
+ exit(0);
+ }
+
+ if (master_key_fd != -1 && random_key_flag)
+ krb5_errx(context, 1, "random-key and master-key-fd "
+ "is mutual exclusive");
+
+ if (keyfile == NULL) {
+ aret = asprintf(&keyfile, "%s/m-key", hdb_db_dir(context));
+ if (aret == -1)
+ krb5_errx(context, 1, "out of memory");
+ }
+
+ ret = krb5_string_to_enctype(context, enctype_str, &enctype);
+ if(ret)
+ krb5_err(context, 1, ret, "krb5_string_to_enctype");
+
+ ret = hdb_read_master_key(context, keyfile, &mkey);
+ if(ret && ret != ENOENT)
+ krb5_err(context, 1, ret, "reading master key from %s", keyfile);
+
+ if (convert_flag) {
+ if (ret)
+ krb5_err(context, 1, ret, "reading master key from %s", keyfile);
+ } else {
+ krb5_keyblock key;
+ krb5_salt salt;
+ salt.salttype = KRB5_PW_SALT;
+ /* XXX better value? */
+ salt.saltvalue.data = NULL;
+ salt.saltvalue.length = 0;
+ if (random_key_flag) {
+ ret = krb5_generate_random_keyblock(context, enctype, &key);
+ if (ret)
+ krb5_err(context, 1, ret, "krb5_generate_random_keyblock");
+
+ } else {
+ if(master_key_fd != -1) {
+ ssize_t n;
+ n = read(master_key_fd, buf, sizeof(buf)-1);
+ if(n <= 0)
+ krb5_err(context, 1, errno, "failed to read passphrase");
+ buf[n] = '\0';
+ buf[strcspn(buf, "\r\n")] = '\0';
+
+ } else {
+ if(UI_UTIL_read_pw_string(buf, sizeof(buf), "Master key: ",
+ UI_UTIL_FLAG_VERIFY))
+ exit(1);
+ }
+ krb5_string_to_key_salt(context, enctype, buf, salt, &key);
+ }
+ ret = hdb_add_master_key(context, &key, &mkey);
+ if (ret)
+ krb5_err(context, 1, ret, "hdb_add_master_key");
+
+ krb5_free_keyblock_contents(context, &key);
+
+ }
+
+ {
+ char *new = NULL, *old = NULL;
+
+ aret = asprintf(&old, "%s.old", keyfile);
+ if (aret == -1) {
+ old = NULL;
+ ret = ENOMEM;
+ goto out;
+ }
+ aret = asprintf(&new, "%s.new", keyfile);
+ if (aret == -1) {
+ new = NULL;
+ ret = ENOMEM;
+ goto out;
+ }
+ if(unlink(new) < 0 && errno != ENOENT) {
+ ret = errno;
+ goto out;
+ }
+ krb5_warnx(context, "writing key to `%s'", keyfile);
+ ret = hdb_write_master_key(context, new, mkey);
+ if(ret)
+ unlink(new);
+ else {
+#ifndef NO_POSIX_LINKS
+ unlink(old);
+ if(link(keyfile, old) < 0 && errno != ENOENT) {
+ ret = errno;
+ unlink(new);
+ } else {
+#endif
+ if(rename(new, keyfile) < 0) {
+ ret = errno;
+ }
+#ifndef NO_POSIX_LINKS
+ }
+#endif
+ }
+ out:
+ free(old);
+ free(new);
+ if(ret)
+ krb5_warn(context, errno, "writing master key file");
+ }
+
+ hdb_free_master_key(context, mkey);
+
+ exit(ret != 0);
+}
diff --git a/third_party/heimdal/kdc/kx509.c b/third_party/heimdal/kdc/kx509.c
new file mode 100644
index 0000000..6efd94e
--- /dev/null
+++ b/third_party/heimdal/kdc/kx509.c
@@ -0,0 +1,1080 @@
+/*
+ * Copyright (c) 2006 - 2019 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "kdc_locl.h"
+#include <hex.h>
+#include <rfc2459_asn1.h>
+#include <hx509.h>
+#include <hx509_err.h>
+#include <kx509_err.h>
+
+#include <stdarg.h>
+
+/*
+ * This file implements the kx509 service.
+ *
+ * The protocol, its shortcomings, and its future are described in
+ * lib/krb5/hx509.c. See also lib/asn1/kx509.asn1.
+ *
+ * The service handles requests, decides whether to issue a certificate, and
+ * does so by populating a "template" to generate a TBSCertificate and signing
+ * it with a configured CA issuer certificate and private key. See ca.c for
+ * details.
+ *
+ * A "template" is a Certificate that has ${variable} references in its
+ * subjectName, and may have EKUs.
+ *
+ * Some SANs may be included in issued certificates. See below.
+ *
+ * Besides future protocol improvements described in lib/krb5/hx509.c, here is
+ * a list of KDC functionality we'd like to add:
+ *
+ * - support templates as strings (rather than filenames) in configuration?
+ * - lookup an hx509 template for the client principal in its HDB entry?
+ * - lookup subjectName, SANs for a principal in its HDB entry
+ * - lookup a host-based client principal's HDB entry and add its canonical
+ * name / aliases as dNSName SANs
+ * (this would have to be if requested by the client, perhaps; see
+ * commentary about the protocol in lib/krb5/kx509.c)
+ * - add code to build a template on the fly
+ *
+ * (just SANs, with empty subjectName?
+ * or
+ * CN=component0,CN=component1,..,CN=componentN,DC=<from-REALM>
+ * and set KU and EKUs)
+ *
+ * Processing begins in _kdc_do_kx509().
+ *
+ * The sequence of events in _kdc_do_kx509() is:
+ *
+ * - parse outer request
+ * - authenticate request
+ * - extract CSR and AP-REQ Authenticator authz-data elements
+ * - characterize request as one of
+ * - default client cert req (no cert exts requested, client user princ)
+ * - default server cert req (no cert exts requested, client service princ)
+ * - client cert req (cert exts requested denoting client use)
+ * - server cert req (cert exts requested denoting server use)
+ * - mixed cert req (cert exts requested denoting client and server use)
+ * - authorize request based only on the request's details
+ * - there is a default authorizer, and a plugin authorizer
+ * - get configuration sub-tree corresponding to the request as characterized
+ * - missing configuration sub-tree -> reject (we have multiple ways to
+ * express "no")
+ * - get common config params from that sub-tree
+ * - set TBS template and details from CSR and such
+ * - issue certificate by signing TBS
+ */
+
+#ifdef KX509
+
+static const unsigned char version_2_0[4] = {0 , 0, 2, 0};
+
+/*
+ * Taste the request to see if it's a kx509 request.
+ */
+krb5_error_code
+_kdc_try_kx509_request(kx509_req_context r)
+{
+ const unsigned char *p = (const void *)(uintptr_t)r->request.data;
+ size_t len = r->request.length;
+ size_t sz;
+
+ if (len < sizeof(version_2_0))
+ return -1;
+ if (memcmp(version_2_0, p, sizeof(version_2_0)) != 0)
+ return -1;
+ p += sizeof(version_2_0);
+ len -= sizeof(version_2_0);
+ if (len == 0)
+ return -1;
+ memset(&r->req, 0, sizeof(r->req));
+ return decode_Kx509Request(p, len, &r->req, &sz);
+}
+
+static krb5_boolean
+get_bool_param(krb5_context context,
+ krb5_boolean def,
+ const char *crealm,
+ const char *name)
+{
+ krb5_boolean global_default;
+
+ global_default = krb5_config_get_bool_default(context, NULL, def, "kdc",
+ name, NULL);
+ if (!crealm)
+ return global_default;
+ return krb5_config_get_bool_default(context, NULL, global_default,
+ "kdc", "realms", crealm, name, NULL);
+}
+
+/*
+ * Verify the HMAC in the request.
+ */
+static krb5_error_code
+verify_req_hash(krb5_context context,
+ const Kx509Request *req,
+ krb5_keyblock *key)
+{
+ unsigned char digest[SHA_DIGEST_LENGTH];
+ HMAC_CTX ctx;
+
+ if (req->pk_hash.length != sizeof(digest)) {
+ krb5_set_error_message(context, KRB5KDC_ERR_PREAUTH_FAILED,
+ "pk-hash has wrong length: %lu",
+ (unsigned long)req->pk_hash.length);
+ return KRB5KDC_ERR_PREAUTH_FAILED;
+ }
+
+ HMAC_CTX_init(&ctx);
+ if (HMAC_Init_ex(&ctx, key->keyvalue.data, key->keyvalue.length,
+ EVP_sha1(), NULL) == 0) {
+ HMAC_CTX_cleanup(&ctx);
+ return krb5_enomem(context);
+ }
+ if (sizeof(digest) != HMAC_size(&ctx))
+ krb5_abortx(context, "runtime error, hmac buffer wrong size in kx509");
+ HMAC_Update(&ctx, version_2_0, sizeof(version_2_0));
+ if (req->pk_key.length)
+ HMAC_Update(&ctx, req->pk_key.data, req->pk_key.length);
+ else
+ HMAC_Update(&ctx, req->authenticator.data, req->authenticator.length);
+ HMAC_Final(&ctx, digest, 0);
+ HMAC_CTX_cleanup(&ctx);
+
+ if (ct_memcmp(req->pk_hash.data, digest, sizeof(digest)) != 0) {
+ krb5_set_error_message(context, KRB5KDC_ERR_PREAUTH_FAILED,
+ "kx509 request MAC mismatch");
+ return KRB5KDC_ERR_PREAUTH_FAILED;
+ }
+ return 0;
+}
+
+/*
+ * Set the HMAC in the response.
+ */
+static krb5_error_code
+calculate_reply_hash(krb5_context context,
+ krb5_keyblock *key,
+ Kx509Response *rep)
+{
+ krb5_error_code ret = 0;
+ HMAC_CTX ctx;
+
+ HMAC_CTX_init(&ctx);
+
+ if (HMAC_Init_ex(&ctx, key->keyvalue.data, key->keyvalue.length,
+ EVP_sha1(), NULL) == 0)
+ ret = krb5_enomem(context);
+
+ if (ret == 0)
+ ret = krb5_data_alloc(rep->hash, HMAC_size(&ctx));
+ if (ret) {
+ HMAC_CTX_cleanup(&ctx);
+ return krb5_enomem(context);
+ }
+
+ HMAC_Update(&ctx, version_2_0, sizeof(version_2_0));
+ {
+ int32_t t = rep->error_code;
+ unsigned char encint[sizeof(t) + 1];
+ size_t k;
+
+ /*
+ * RFC6717 says this about how the error-code is included in the HMAC:
+ *
+ * o DER representation of the error-code exclusive of the tag and
+ * length, if it is present.
+ *
+ * So we use der_put_integer(), which encodes from the right.
+ *
+ * RFC6717 does not constrain the error-code's range. We assume it to
+ * be a 32-bit, signed integer, for which we'll need no more than 5
+ * bytes.
+ */
+ ret = der_put_integer(&encint[sizeof(encint) - 1],
+ sizeof(encint), &t, &k);
+ if (ret == 0)
+ HMAC_Update(&ctx, &encint[sizeof(encint)] - k, k);
+ }
+ if (rep->certificate)
+ HMAC_Update(&ctx, rep->certificate->data, rep->certificate->length);
+ if (rep->e_text)
+ HMAC_Update(&ctx, (unsigned char *)*rep->e_text, strlen(*rep->e_text));
+
+ HMAC_Final(&ctx, rep->hash->data, 0);
+ HMAC_CTX_cleanup(&ctx);
+
+ return 0;
+}
+
+static void
+frees(char **s)
+{
+ free(*s);
+ *s = NULL;
+}
+
+/* Check that a krbtgt's second component is a local realm */
+static krb5_error_code
+is_local_realm(krb5_context context,
+ kx509_req_context reqctx,
+ const char *realm)
+{
+ krb5_error_code ret;
+ krb5_principal tgs;
+ HDB *db;
+ hdb_entry *ent = NULL;
+
+ ret = krb5_make_principal(context, &tgs, realm, KRB5_TGS_NAME, realm,
+ NULL);
+ if (ret)
+ return ret;
+ if (ret == 0)
+ ret = _kdc_db_fetch(context, reqctx->config, tgs, HDB_F_GET_KRBTGT,
+ NULL, &db, &ent);
+ if (ent)
+ _kdc_free_ent(context, db, ent);
+ krb5_free_principal(context, tgs);
+ if (ret == HDB_ERR_NOENTRY || ret == HDB_ERR_NOT_FOUND_HERE)
+ return KRB5KRB_AP_ERR_NOT_US;
+ return ret;
+}
+
+/*
+ * Since we're using the HDB as a keytab we have to check that the client used
+ * an acceptable name for the kx509 service.
+ *
+ * We accept two names: kca_service/hostname and krbtgt/REALM.
+ *
+ * We allow cross-realm requests.
+ *
+ * Maybe x-realm support should be configurable. Requiring INITIAL tickets
+ * does NOT preclude x-realm support! (Cross-realm TGTs can be INITIAL.)
+ *
+ * Support for specific client realms is configurable by configuring issuer
+ * credentials and TBS templates on a per-realm basis and configuring no
+ * default. But maybe we should have an explicit configuration parameter
+ * to enable support for clients from different realms than the service.
+ */
+static krb5_error_code
+kdc_kx509_verify_service_principal(krb5_context context,
+ kx509_req_context reqctx,
+ krb5_principal sprincipal)
+{
+ krb5_error_code ret = 0;
+ krb5_principal principal = NULL;
+ char *expected = NULL;
+ char localhost[MAXHOSTNAMELEN];
+
+ if (krb5_principal_get_num_comp(context, sprincipal) != 2)
+ goto err;
+
+ /* Check if sprincipal is a krbtgt/REALM name */
+ if (strcmp(krb5_principal_get_comp_string(context, sprincipal, 0),
+ KRB5_TGS_NAME) == 0) {
+ const char *r = krb5_principal_get_comp_string(context, sprincipal, 1);
+ if ((ret = is_local_realm(context, reqctx, r)))
+ kdc_audit_addreason((kdc_request_t)reqctx,
+ "Client used wrong krbtgt for kx509");
+ goto out;
+ }
+
+ /* Must be hostbased kca_service name then */
+ ret = gethostname(localhost, sizeof(localhost) - 1);
+ if (ret != 0) {
+ ret = errno;
+ kdc_log(context, reqctx->config, 0, "Failed to get local hostname");
+ kdc_audit_addreason((kdc_request_t)reqctx,
+ "Failed to get local hostname");
+ return ret;
+ }
+ localhost[sizeof(localhost) - 1] = '\0';
+
+ ret = krb5_make_principal(context, &principal, "", "kca_service",
+ localhost, NULL);
+ if (ret)
+ goto out;
+
+ if (krb5_principal_compare_any_realm(context, sprincipal, principal))
+ goto out; /* found a match */
+
+err:
+ ret = krb5_unparse_name(context, sprincipal, &expected);
+ if (ret)
+ goto out;
+
+ ret = KRB5KDC_ERR_SERVER_NOMATCH;
+ kdc_audit_addreason((kdc_request_t)reqctx, "Client used wrong kx509 "
+ "service principal (expected %s)", expected);
+
+out:
+ krb5_xfree(expected);
+ krb5_free_principal(context, principal);
+
+ return ret;
+}
+
+static krb5_error_code
+encode_reply(krb5_context context,
+ kx509_req_context reqctx,
+ Kx509Response *r)
+{
+ krb5_error_code ret;
+ krb5_data data;
+ size_t size;
+
+ reqctx->reply->data = NULL;
+ reqctx->reply->length = 0;
+ ASN1_MALLOC_ENCODE(Kx509Response, data.data, data.length, r, &size, ret);
+ if (ret)
+ return ret;
+ if (size != data.length)
+ krb5_abortx(context, "ASN1 internal error");
+
+ ret = krb5_data_alloc(reqctx->reply, data.length + sizeof(version_2_0));
+ if (ret == 0) {
+ memcpy(reqctx->reply->data, version_2_0, sizeof(version_2_0));
+ memcpy(((unsigned char *)reqctx->reply->data) + sizeof(version_2_0),
+ data.data, data.length);
+ }
+ free(data.data);
+ return ret;
+}
+
+/* Make an error response, and log the error message as well */
+static krb5_error_code
+mk_error_response(krb5_context context,
+ kx509_req_context reqctx,
+ int level,
+ int32_t code,
+ const char *fmt,
+ ...)
+{
+ krb5_error_code ret = code;
+ krb5_error_code ret2;
+ Kx509Response rep;
+ const char *msg;
+ char *freeme0 = NULL;
+ char *freeme1 = NULL;
+ va_list ap;
+
+ if (code != 0) {
+ /* Log errors where _kdc_audit_trail() is not enough */
+ if (code == ENOMEM)
+ level = 0;
+ if (level < 3) {
+ va_start(ap, fmt);
+ kdc_vlog(context, reqctx->config, level, fmt, ap);
+ va_end(ap);
+ }
+
+ va_start(ap, fmt);
+ kdc_audit_vaddreason((kdc_request_t)reqctx, fmt, ap);
+ va_end(ap);
+ }
+
+ if (!reqctx->config->enable_kx509)
+ code = KRB5KDC_ERR_POLICY;
+
+ /* Make sure we only send RFC4120 and friends wire protocol error codes */
+ if (code) {
+ if (code == KX509_ERR_NONE) {
+ code = 0;
+ } else if (code > KX509_ERR_NONE && code <= KX509_ERR_SRV_OVERLOADED) {
+ code -= KX509_ERR_NONE;
+ } else {
+ if (code < KRB5KDC_ERR_NONE || code >= KRB5_ERR_RCSID)
+ code = KRB5KRB_ERR_GENERIC;
+ code -= KRB5KDC_ERR_NONE;
+ code += kx509_krb5_error_base;
+ }
+ }
+
+ va_start(ap, fmt);
+ if (vasprintf(&freeme0, fmt, ap) == -1 || freeme0 == NULL)
+ msg = "Could not format error message (out of memory)";
+ else
+ msg = freeme0;
+ va_end(ap);
+
+ if (!reqctx->config->enable_kx509 &&
+ asprintf(&freeme1, "kx509 service is disabled (%s)", msg) > -1 &&
+ freeme1 != NULL) {
+ msg = freeme1;
+ }
+
+ rep.hash = NULL;
+ rep.certificate = NULL;
+ rep.error_code = code;
+ if (ALLOC(rep.e_text))
+ *rep.e_text = (void *)(uintptr_t)msg;
+
+ if (reqctx->key) {
+ if (ALLOC(rep.hash) != NULL &&
+ calculate_reply_hash(context, reqctx->key, &rep)) {
+ free(rep.hash);
+ rep.hash = NULL;
+ }
+ }
+
+ if ((ret2 = encode_reply(context, reqctx, &rep)))
+ ret = ret2;
+ if (rep.hash)
+ krb5_data_free(rep.hash);
+ free(rep.e_text);
+ free(rep.hash);
+ free(freeme0);
+ free(freeme1);
+ return ret;
+}
+
+/* Wrap a bare public (RSA) key with a CSR (not signed it, since we can't) */
+static krb5_error_code
+make_csr(krb5_context context, kx509_req_context reqctx, krb5_data *key)
+{
+ krb5_error_code ret;
+ SubjectPublicKeyInfo spki;
+ heim_any any;
+
+ ret = hx509_request_init(context->hx509ctx, &reqctx->csr);
+ if (ret)
+ return ret;
+
+ memset(&spki, 0, sizeof(spki));
+ spki.subjectPublicKey.data = key->data;
+ spki.subjectPublicKey.length = key->length * 8;
+
+ ret = der_copy_oid(&asn1_oid_id_pkcs1_rsaEncryption,
+ &spki.algorithm.algorithm);
+
+ any.data = "\x05\x00";
+ any.length = 2;
+ spki.algorithm.parameters = &any;
+
+ if (ret == 0)
+ ret = hx509_request_set_SubjectPublicKeyInfo(context->hx509ctx,
+ reqctx->csr, &spki);
+ der_free_oid(&spki.algorithm.algorithm);
+ if (ret)
+ hx509_request_free(&reqctx->csr);
+
+ /*
+ * TODO: Move a lot of the templating stuff here so we can let clients
+ * leave out extensions they don't want.
+ */
+ return ret;
+}
+
+/* Update a CSR with desired Certificate Extensions */
+static krb5_error_code
+update_csr(krb5_context context, kx509_req_context reqctx, Extensions *exts)
+{
+ krb5_error_code ret = 0;
+ size_t i, k;
+
+ if (exts == NULL)
+ return 0;
+
+ for (i = 0; ret == 0 && i < exts->len; i++) {
+ Extension *e = &exts->val[i];
+
+ if (der_heim_oid_cmp(&e->extnID, &asn1_oid_id_x509_ce_keyUsage) == 0) {
+ KeyUsage ku;
+
+ ret = decode_KeyUsage(e->extnValue.data, e->extnValue.length, &ku,
+ NULL);
+ if (ret)
+ return ret;
+ ret = hx509_request_set_ku(context->hx509ctx, reqctx->csr, ku);
+ } else if (der_heim_oid_cmp(&e->extnID,
+ &asn1_oid_id_x509_ce_extKeyUsage) == 0) {
+ ExtKeyUsage eku;
+
+ ret = decode_ExtKeyUsage(e->extnValue.data, e->extnValue.length,
+ &eku, NULL);
+ for (k = 0; ret == 0 && k < eku.len; k++) {
+ ret = hx509_request_add_eku(context->hx509ctx, reqctx->csr,
+ &eku.val[k]);
+ }
+ free_ExtKeyUsage(&eku);
+ } else if (der_heim_oid_cmp(&e->extnID,
+ &asn1_oid_id_x509_ce_subjectAltName) == 0) {
+ GeneralNames san;
+
+ ret = decode_GeneralNames(e->extnValue.data, e->extnValue.length,
+ &san, NULL);
+ for (k = 0; ret == 0 && k < san.len; k++) {
+ ret = hx509_request_add_GeneralName(context->hx509ctx,
+ reqctx->csr, &san.val[k]);
+ }
+ free_GeneralNames(&san);
+ }
+ }
+ if (ret) {
+ const char *emsg = krb5_get_error_message(context, ret);
+ kdc_log(context, reqctx->config, 1,
+ "Error handling requested extensions: %s", emsg);
+ kdc_audit_addreason((kdc_request_t)reqctx,
+ "Error handling requested extensions: %s",
+ emsg);
+ krb5_free_error_message(context, emsg);
+ }
+ return ret;
+}
+
+
+/*
+ * Parse the `pk_key' from the request as a CSR or raw public key, and if the
+ * latter, wrap it in a non-signed CSR.
+ */
+static krb5_error_code
+get_csr(krb5_context context, kx509_req_context reqctx)
+{
+ krb5_error_code ret;
+ RSAPublicKey rsapkey;
+ heim_octet_string pk_key = reqctx->req.pk_key;
+ size_t size;
+
+ ret = decode_Kx509CSRPlus(pk_key.data, pk_key.length, &reqctx->csr_plus,
+ &size);
+ if (ret == 0) {
+ reqctx->have_csr = 1;
+ reqctx->send_chain = 1;
+
+ /* Parse CSR */
+ ret = hx509_request_parse_der(context->hx509ctx, &reqctx->csr_plus.csr,
+ &reqctx->csr);
+ /*
+ * Handle any additional Certificate Extensions requested out of band
+ * of the CSR.
+ */
+ if (ret == 0)
+ return update_csr(context, reqctx, reqctx->csr_plus.exts);
+ kdc_audit_addreason((kdc_request_t)reqctx, "Invalid CSR");
+ return ret;
+ }
+ reqctx->send_chain = 0;
+ reqctx->have_csr = 0;
+
+ /* Check if proof of possession is required by configuration */
+ if (!get_bool_param(context, FALSE, reqctx->realm, "require_csr")) {
+ kdc_audit_addreason((kdc_request_t)reqctx,
+ "CSRs required but client did not send one");
+ krb5_set_error_message(context, KX509_STATUS_CLIENT_USE_CSR,
+ "CSRs required but kx509 client did not send "
+ "one");
+ return KX509_STATUS_CLIENT_USE_CSR;
+ }
+
+ /* Attempt to decode pk_key as RSAPublicKey */
+ ret = decode_RSAPublicKey(reqctx->req.pk_key.data,
+ reqctx->req.pk_key.length,
+ &rsapkey, &size);
+ free_RSAPublicKey(&rsapkey);
+ if (ret == 0 && size == reqctx->req.pk_key.length)
+ return make_csr(context, reqctx, &pk_key); /* Make pretend CSR */
+
+ /* Not an RSAPublicKey or garbage follows it */
+ if (ret == 0) {
+ ret = KRB5KDC_ERR_NULL_KEY;
+ kdc_audit_addreason((kdc_request_t)reqctx,
+ "Request has garbage after key");
+ krb5_set_error_message(context, ret, "Request has garbage after key");
+ return ret;
+ }
+
+ kdc_audit_addreason((kdc_request_t)reqctx,
+ "Could not decode CSR or RSA subject public key");
+ krb5_set_error_message(context, ret,
+ "Could not decode CSR or RSA subject public key");
+ return ret;
+}
+
+/*
+ * Host-based principal _clients_ might ask for a cert for their host -- but
+ * which services are permitted to do that? This function answers that
+ * question.
+ */
+static int
+check_authz_svc_ok(krb5_context context, const char *svc)
+{
+ const char *def[] = { "host", "HTTP", 0 };
+ const char * const *svcs;
+ char **strs;
+
+ strs = krb5_config_get_strings(context, NULL, "kdc",
+ "kx509_permitted_hostbased_services", NULL);
+ for (svcs = strs ? (const char * const *)strs : def; svcs[0]; svcs++) {
+ if (strcmp(svcs[0], svc) == 0) {
+ krb5_config_free_strings(strs);
+ return 1;
+ }
+ }
+ krb5_config_free_strings(strs);
+ return 0;
+}
+
+static krb5_error_code
+check_authz(krb5_context context,
+ kx509_req_context reqctx,
+ krb5_principal cprincipal)
+{
+ krb5_error_code ret;
+ const char *comp0 = krb5_principal_get_comp_string(context, cprincipal, 0);
+ const char *comp1 = krb5_principal_get_comp_string(context, cprincipal, 1);
+ unsigned int ncomp = krb5_principal_get_num_comp(context, cprincipal);
+ hx509_san_type san_type;
+ KeyUsage ku, ku_allowed;
+ size_t i;
+ const heim_oid *eku_whitelist[] = {
+ &asn1_oid_id_pkix_kp_serverAuth,
+ &asn1_oid_id_pkix_kp_clientAuth,
+ &asn1_oid_id_pkekuoid,
+ &asn1_oid_id_pkinit_ms_eku
+ };
+ char *cprinc = NULL;
+ char *s = NULL;
+
+ /*
+ * In the no-CSR case we'll derive cert contents from client name and its
+ * HDB entry -- authorization is implied.
+ */
+ if (!reqctx->have_csr)
+ return 0;
+ ret = kdc_authorize_csr(context, reqctx->config->app, reqctx->csr,
+ cprincipal);
+ if (ret == 0) {
+ kdc_audit_setkv_bool((kdc_request_t)reqctx, "authorized", TRUE);
+
+ ret = hx509_request_get_san(reqctx->csr, 0, &san_type, &s);
+ if (ret == 0) {
+ const char *san_type_s;
+
+ /* This should be an hx509 function... */
+ switch (san_type) {
+ case HX509_SAN_TYPE_EMAIL: san_type_s = "rfc822Name"; break;
+ case HX509_SAN_TYPE_DNSNAME: san_type_s = "dNSName"; break;
+ case HX509_SAN_TYPE_DN: san_type_s = "DN"; break;
+ case HX509_SAN_TYPE_REGISTERED_ID: san_type_s = "registeredID"; break;
+ case HX509_SAN_TYPE_XMPP: san_type_s = "xMPPName"; break;
+ case HX509_SAN_TYPE_PKINIT: san_type_s = "krb5PrincipalName"; break;
+ case HX509_SAN_TYPE_MS_UPN: san_type_s = "ms-UPN"; break;
+ default: san_type_s = "unknown"; break;
+ }
+ kdc_audit_addkv((kdc_request_t)reqctx, 0, "san0_type", "%s",
+ san_type_s);
+ kdc_audit_addkv((kdc_request_t)reqctx, 0, "san0", "%s", s);
+ }
+ frees(&s);
+ ret = hx509_request_get_eku(reqctx->csr, 0, &s);
+ if (ret == 0)
+ kdc_audit_addkv((kdc_request_t)reqctx, 0, "eku0", "%s", s);
+ free(s);
+ return 0;
+ }
+ if (ret != KRB5_PLUGIN_NO_HANDLE) {
+ kdc_audit_addreason((kdc_request_t)reqctx,
+ "Requested extensions rejected by plugin");
+ return ret;
+ }
+
+ /* Default authz */
+ if ((ret = krb5_unparse_name(context, cprincipal, &cprinc)))
+ return ret;
+
+ for (i = 0; ret == 0; i++) {
+
+ frees(&s);
+ ret = hx509_request_get_san(reqctx->csr, i, &san_type, &s);
+ if (ret)
+ break;
+ switch (san_type) {
+ case HX509_SAN_TYPE_DNSNAME:
+ if (ncomp != 2 || strcasecmp(comp1, s) != 0 ||
+ strchr(s, '.') == NULL ||
+ !check_authz_svc_ok(context, comp0)) {
+ kdc_audit_addreason((kdc_request_t)reqctx,
+ "Requested extensions rejected by "
+ "default policy (dNSName SAN "
+ "does not match client)");
+ goto eacces;
+ }
+ break;
+ case HX509_SAN_TYPE_PKINIT:
+ if (strcmp(cprinc, s) != 0) {
+ kdc_audit_addreason((kdc_request_t)reqctx,
+ "Requested extensions rejected by "
+ "default policy (PKINIT SAN "
+ "does not match client)");
+ goto eacces;
+ }
+ break;
+ default:
+ kdc_audit_addreason((kdc_request_t)reqctx,
+ "Requested extensions rejected by "
+ "default policy (non-default SAN "
+ "requested)");
+ goto eacces;
+ }
+ }
+ frees(&s);
+ if (ret == HX509_NO_ITEM)
+ ret = 0;
+ if (ret)
+ goto out;
+
+ for (i = 0; ret == 0; i++) {
+ heim_oid oid;
+ size_t k;
+
+ frees(&s);
+ ret = hx509_request_get_eku(reqctx->csr, i, &s);
+ if (ret)
+ break;
+
+ if ((ret = der_parse_heim_oid(s, ".", &oid))) {
+ goto out;
+ }
+ for (k = 0; k < sizeof(eku_whitelist)/sizeof(eku_whitelist[0]); k++) {
+ if (der_heim_oid_cmp(eku_whitelist[k], &oid) == 0)
+ break;
+ }
+ der_free_oid(&oid);
+ if (k == sizeof(eku_whitelist)/sizeof(eku_whitelist[0])) {
+ kdc_audit_addreason((kdc_request_t)reqctx,
+ "Requested EKU rejected by default policy");
+ goto eacces;
+ }
+ }
+ if (ret == HX509_NO_ITEM)
+ ret = 0;
+ if (ret)
+ goto out;
+
+ memset(&ku_allowed, 0, sizeof(ku_allowed));
+ ku_allowed.digitalSignature = 1;
+ ku_allowed.nonRepudiation = 1;
+ ret = hx509_request_get_ku(context->hx509ctx, reqctx->csr, &ku);
+ if (ret)
+ goto out;
+ if (KeyUsage2int(ku) != (KeyUsage2int(ku) & KeyUsage2int(ku_allowed)))
+ goto eacces;
+
+ kdc_audit_setkv_bool((kdc_request_t)reqctx, "authorized", TRUE);
+ free(cprinc);
+ return 0;
+
+eacces:
+ ret = EACCES;
+ goto out2;
+
+out:
+ /* XXX Display error code */
+ kdc_audit_addreason((kdc_request_t)reqctx,
+ "Error handling requested extensions");
+out2:
+ free(cprinc);
+ free(s);
+ return ret;
+}
+
+static int
+chain_add1_func(hx509_context context, void *d, hx509_cert c)
+{
+ heim_octet_string os;
+ Certificates *cs = d;
+ Certificate c2;
+ int ret;
+
+ ret = hx509_cert_binary(context, c, &os);
+ if (ret)
+ return ret;
+ ret = decode_Certificate(os.data, os.length, &c2, NULL);
+ der_free_octet_string(&os);
+ if (ret)
+ return ret;
+ ret = add_Certificates(cs, &c2);
+ free_Certificate(&c2);
+ return ret;
+}
+
+static krb5_error_code
+encode_cert_and_chain(hx509_context hx509ctx,
+ hx509_certs certs,
+ krb5_data *out)
+{
+ krb5_error_code ret;
+ Certificates cs;
+ size_t len;
+
+ cs.len = 0;
+ cs.val = 0;
+
+ ret = hx509_certs_iter_f(hx509ctx, certs, chain_add1_func, &cs);
+ if (ret == 0)
+ ASN1_MALLOC_ENCODE(Certificates, out->data, out->length,
+ &cs, &len, ret);
+ free_Certificates(&cs);
+ return ret;
+}
+
+/*
+ * Process a request, produce a reply.
+ */
+
+krb5_error_code
+_kdc_do_kx509(kx509_req_context r)
+{
+ krb5_error_code ret = 0;
+ krb5_ticket *ticket = NULL;
+ krb5_flags ap_req_options;
+ krb5_principal cprincipal = NULL;
+ krb5_principal sprincipal = NULL;
+ krb5_keytab id = NULL;
+ Kx509Response rep;
+ hx509_certs certs = NULL;
+ int is_probe = 0;
+
+ r->csr_plus.csr.data = NULL;
+ r->csr_plus.exts = NULL;
+ r->sname = NULL;
+ r->cname = NULL;
+ r->realm = NULL;
+ r->key = NULL;
+ r->csr = NULL;
+ r->ac = NULL;
+
+ /*
+ * In order to support authenticated error messages we defer checking
+ * whether the kx509 service is enabled until after accepting the AP-REQ.
+ */
+
+ krb5_data_zero(r->reply);
+ memset(&rep, 0, sizeof(rep));
+
+ if (r->req.authenticator.length == 0) {
+ /*
+ * Unauthenticated kx509 service availability probe.
+ *
+ * mk_error_response() will check whether the service is enabled and
+ * possibly change the error code and message.
+ */
+ is_probe = 1;
+ kdc_audit_addkv((kdc_request_t)r, 0, "probe", "unauthenticated");
+ ret = mk_error_response(r->context, r, 4, 0,
+ "kx509 service is available");
+ goto out;
+ }
+
+ /* Authenticate the request (consume the AP-REQ) */
+ ret = krb5_kt_resolve(r->context, "HDBGET:", &id);
+ if (ret) {
+ const char *msg = krb5_get_error_message(r->context, ret);
+ ret = mk_error_response(r->context, r, 1,
+ KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN,
+ "Can't open HDB/keytab for kx509: %s",
+ msg);
+ krb5_free_error_message(r->context, msg);
+ goto out;
+ }
+
+ ret = krb5_rd_req(r->context,
+ &r->ac,
+ &r->req.authenticator,
+ NULL,
+ id,
+ &ap_req_options,
+ &ticket);
+ if (ret == 0)
+ ret = krb5_auth_con_getkey(r->context, r->ac, &r->key);
+ if (ret == 0 && r->key == NULL)
+ ret = KRB5KDC_ERR_NULL_KEY;
+ /*
+ * Provided we got the session key, errors past this point will be
+ * authenticated.
+ */
+ if (ret == 0)
+ ret = krb5_ticket_get_client(r->context, ticket, &cprincipal);
+
+ /* Optional: check if Ticket is INITIAL */
+ if (ret == 0 &&
+ !ticket->ticket.flags.initial &&
+ !get_bool_param(r->context, TRUE,
+ krb5_principal_get_realm(r->context, cprincipal),
+ "require_initial_kca_tickets")) {
+ ret = mk_error_response(r->context, r, 4, KRB5KDC_ERR_POLICY,
+ "Client used non-INITIAL tickets, but kx509 "
+ "service is configured to require INITIAL "
+ "tickets");
+ goto out;
+ }
+
+ if (ret == 0)
+ ret = krb5_unparse_name(r->context, cprincipal, &r->cname);
+
+ /* Check that the service name is a valid kx509 service name */
+ if (ret == 0)
+ ret = krb5_ticket_get_server(r->context, ticket, &sprincipal);
+ if (ret == 0)
+ r->realm = krb5_principal_get_realm(r->context, sprincipal);
+ if (ret == 0)
+ ret = krb5_unparse_name(r->context, sprincipal, &r->sname);
+ if (ret == 0)
+ ret = kdc_kx509_verify_service_principal(r->context, r, sprincipal);
+ if (ret) {
+ ret = mk_error_response(r->context, r, 4, ret,
+ "kx509 client used incorrect service name");
+ goto out;
+ }
+
+ /* Authenticate the rest of the request */
+ ret = verify_req_hash(r->context, &r->req, r->key);
+ if (ret) {
+ ret = mk_error_response(r->context, r, 4, ret,
+ "Incorrect request HMAC on kx509 request");
+ goto out;
+ }
+
+ if (r->req.pk_key.length == 0) {
+ /*
+ * The request is an authenticated kx509 service availability probe.
+ *
+ * mk_error_response() will check whether the service is enabled and
+ * possibly change the error code and message.
+ */
+ is_probe = 1;
+ kdc_audit_addkv((kdc_request_t)r, 0, "probe", "authenticated");
+ ret = mk_error_response(r->context, r, 4, 0,
+ "kx509 authenticated probe request");
+ goto out;
+ }
+
+ /* Extract and parse CSR or a DER-encoded RSA public key */
+ ret = get_csr(r->context, r);
+ if (ret) {
+ const char *msg = krb5_get_error_message(r->context, ret);
+ ret = mk_error_response(r->context, r, 3, ret,
+ "Failed to parse CSR: %s", msg);
+ krb5_free_error_message(r->context, msg);
+ goto out;
+ }
+
+ /* Authorize the request */
+ ret = check_authz(r->context, r, cprincipal);
+ if (ret) {
+ const char *msg = krb5_get_error_message(r->context, ret);
+ ret = mk_error_response(r->context, r, 3, ret,
+ "Rejected by policy: %s", msg);
+ krb5_free_error_message(r->context, msg);
+ goto out;
+ }
+
+ /* Issue the certificate */
+ ALLOC(rep.hash);
+ ALLOC(rep.certificate);
+ if (rep.certificate == NULL || rep.hash == NULL) {
+ ret = mk_error_response(r->context, r, 0, ENOMEM,
+ "Could allocate memory for response");
+ goto out;
+ }
+
+ krb5_data_zero(rep.hash);
+ krb5_data_zero(rep.certificate);
+ krb5_ticket_get_times(r->context, ticket, &r->ticket_times);
+ ret = kdc_issue_certificate(r->context, r->config->app, r->logf, r->csr,
+ cprincipal, &r->ticket_times, 0 /*req_life*/,
+ r->send_chain, &certs);
+ if (ret) {
+ int level = 1;
+ const char *msg = krb5_get_error_message(r->context, ret);
+
+ if (ret == KRB5KDC_ERR_POLICY)
+ level = 4; /* _kdc_audit_trail() logs at level 3 */
+ ret = mk_error_response(r->context, r, level, ret,
+ "Certificate isuance failed: %s", msg);
+ krb5_free_error_message(r->context, msg);
+ goto out;
+ }
+
+ ret = encode_cert_and_chain(r->context->hx509ctx, certs, rep.certificate);
+ if (ret) {
+ const char *msg = krb5_get_error_message(r->context, ret);
+ ret = mk_error_response(r->context, r, 1, ret,
+ "Could not encode certificate and chain: %s",
+ msg);
+ krb5_free_error_message(r->context, msg);
+ goto out;
+ }
+
+ /* Authenticate the response */
+ ret = calculate_reply_hash(r->context, r->key, &rep);
+ if (ret) {
+ ret = mk_error_response(r->context, r, 1, ret,
+ "Failed to compute response HMAC");
+ goto out;
+ }
+
+ /* Encode and output reply */
+ ret = encode_reply(r->context, r, &rep);
+ if (ret)
+ /* Can't send an error message either in this case, surely */
+ kdc_audit_addreason((kdc_request_t)r, "Could not encode response");
+
+out:
+ hx509_certs_free(&certs);
+ if (ret == 0 && !is_probe)
+ kdc_audit_setkv_bool((kdc_request_t)r, "cert_issued", TRUE);
+ else
+ kdc_audit_setkv_bool((kdc_request_t)r, "cert_issued", FALSE);
+ if (r->ac)
+ krb5_auth_con_free(r->context, r->ac);
+ if (ticket)
+ krb5_free_ticket(r->context, ticket);
+ if (id)
+ krb5_kt_close(r->context, id);
+ if (sprincipal)
+ krb5_free_principal(r->context, sprincipal);
+ if (cprincipal)
+ krb5_free_principal(r->context, cprincipal);
+ if (r->key)
+ krb5_free_keyblock (r->context, r->key);
+ hx509_request_free(&r->csr);
+ free_Kx509CSRPlus(&r->csr_plus);
+ free_Kx509Response(&rep);
+ free_Kx509Request(&r->req);
+
+ return ret;
+}
+
+#endif /* KX509 */
diff --git a/third_party/heimdal/kdc/libkdc-exports.def b/third_party/heimdal/kdc/libkdc-exports.def
new file mode 100644
index 0000000..1d42b8c
--- /dev/null
+++ b/third_party/heimdal/kdc/libkdc-exports.def
@@ -0,0 +1,107 @@
+EXPORTS
+ kdc_authorize_csr
+ kdc_get_instance
+ kdc_issue_certificate
+ kdc_log
+ kdc_log_msg
+ kdc_log_msg_va
+ kdc_openlog
+ kdc_check_flags
+ kdc_validate_token
+ krb5_kdc_plugin_init
+ krb5_kdc_get_config
+ krb5_kdc_get_time
+ krb5_kdc_pkinit_config
+ krb5_kdc_set_dbinfo
+ krb5_kdc_process_krb5_request
+ krb5_kdc_process_request
+ krb5_kdc_save_request
+ krb5_kdc_update_time
+ krb5_kdc_pk_initialize
+ kdc_request_set_attribute
+ kdc_request_get_attribute
+ kdc_request_copy_attribute
+ kdc_request_delete_attribute
+ kdc_request_add_encrypted_padata
+ kdc_request_add_pac_buffer
+ kdc_request_add_reply_padata
+ kdc_request_get_addr
+ kdc_request_get_armor_client
+ kdc_request_get_armor_clientdb
+ kdc_request_get_armor_pac
+ kdc_request_get_armor_server
+ kdc_request_get_canon_client_princ
+ kdc_request_get_client
+ kdc_request_get_clientdb
+ kdc_request_get_client_princ
+ kdc_request_get_context
+ kdc_request_get_config
+ kdc_request_get_cname
+ kdc_request_get_error_code
+ kdc_request_get_explicit_armor_pac
+ kdc_request_get_explicit_armor_clientdb
+ kdc_request_get_explicit_armor_client
+ kdc_request_get_explicit_armor_present
+ kdc_request_get_explicit_armor_server
+ kdc_request_get_from
+ kdc_request_get_krbtgt
+ kdc_request_get_krbtgtdb
+ kdc_request_get_krbtgt_princ
+ kdc_request_get_pac
+ kdc_request_get_pac_attributes
+ kdc_request_get_rep
+ kdc_request_get_reply_key
+ kdc_request_get_req
+ kdc_request_get_request
+ kdc_request_get_server
+ kdc_request_get_serverdb
+ kdc_request_get_server_princ
+ kdc_request_get_sname
+ kdc_request_get_ticket
+ kdc_request_get_tv_end
+ kdc_request_get_tv_start
+ kdc_request_set_canon_client_princ
+ kdc_request_set_client_princ
+ kdc_request_set_cname
+ kdc_request_set_e_data
+ kdc_request_set_error_code
+ kdc_request_set_krbtgt_princ
+ kdc_request_set_pac
+ kdc_request_set_pac_attributes
+ kdc_request_set_rep
+ kdc_request_set_reply_key
+ kdc_request_set_server_princ
+ kdc_request_set_sname
+ kdc_audit_addkv
+ kdc_audit_addkv_number
+ kdc_audit_addkv_object
+ kdc_audit_addkv_timediff
+ kdc_audit_addaddrs
+ kdc_audit_addreason
+ kdc_audit_getkv
+ kdc_audit_setkv_bool
+ kdc_audit_setkv_number
+ kdc_audit_setkv_object
+ kdc_audit_vaddkv
+ kdc_audit_vaddreason
+ _kdc_audit_trail
+
+ kdc_object_alloc
+ kdc_object_retain
+ kdc_object_release
+ kdc_bool_create
+ kdc_bool_get_value
+ kdc_array_iterate
+ kdc_array_get_length
+ kdc_array_get_value
+ kdc_array_copy_value
+ kdc_string_create
+ kdc_string_get_utf8
+ kdc_data_create
+ kdc_data_get_data
+ kdc_number_create
+ kdc_number_get_value
+
+ ; needed for digest-service
+ _kdc_db_fetch
+ _kdc_free_ent
diff --git a/third_party/heimdal/kdc/libkdc-version.rc b/third_party/heimdal/kdc/libkdc-version.rc
new file mode 100644
index 0000000..fee5004
--- /dev/null
+++ b/third_party/heimdal/kdc/libkdc-version.rc
@@ -0,0 +1,36 @@
+/***********************************************************************
+ * Copyright (c) 2010, Secure Endpoints Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ **********************************************************************/
+
+#define RC_FILE_TYPE VFT_DLL
+#define RC_FILE_DESC_0409 "Heimdal KDC Library"
+#define RC_FILE_ORIG_0409 "libkdc.dll"
+
+#include "../windows/version.rc"
diff --git a/third_party/heimdal/kdc/log.c b/third_party/heimdal/kdc/log.c
new file mode 100644
index 0000000..bfb0f54
--- /dev/null
+++ b/third_party/heimdal/kdc/log.c
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 1997, 1998, 2002 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "kdc_locl.h"
+
+KDC_LIB_FUNCTION void KDC_LIB_CALL
+kdc_openlog(krb5_context context,
+ const char *service,
+ krb5_kdc_configuration *config)
+{
+ char **s = NULL, **p;
+ krb5_initlog(context, "kdc", &config->logf);
+ s = krb5_config_get_strings(context, NULL, service, "logging", NULL);
+ if(s == NULL)
+ s = krb5_config_get_strings(context, NULL, "logging", service, NULL);
+ if(s){
+ for(p = s; *p; p++)
+ krb5_addlog_dest(context, config->logf, *p);
+ krb5_config_free_strings(s);
+ }else {
+ char *ss;
+ if (asprintf(&ss, "0-1/FILE:%s/%s", hdb_db_dir(context),
+ KDC_LOG_FILE) < 0)
+ err(1, "out of memory");
+ krb5_addlog_dest(context, config->logf, ss);
+ free(ss);
+ }
+ krb5_set_warn_dest(context, config->logf);
+}
+
+#undef __attribute__
+#define __attribute__(X)
+
+KDC_LIB_FUNCTION char * KDC_LIB_CALL
+kdc_log_msg_va(krb5_context context,
+ krb5_kdc_configuration *config,
+ int level, const char *fmt, va_list ap)
+ __attribute__ ((__format__ (__printf__, 4, 0)))
+{
+ char *msg;
+ krb5_vlog_msg(context, config->logf, &msg, level, fmt, ap);
+ return msg;
+}
+
+KDC_LIB_FUNCTION char * KDC_LIB_CALL
+kdc_log_msg(krb5_context context,
+ krb5_kdc_configuration *config,
+ int level, const char *fmt, ...)
+ __attribute__ ((__format__ (__printf__, 4, 5)))
+{
+ va_list ap;
+ char *s;
+ va_start(ap, fmt);
+ s = kdc_log_msg_va(context, config, level, fmt, ap);
+ va_end(ap);
+ return s;
+}
+
+KDC_LIB_FUNCTION void KDC_LIB_CALL
+kdc_vlog(krb5_context context,
+ krb5_kdc_configuration *config,
+ int level, const char *fmt, va_list ap)
+ __attribute__ ((__format__ (__printf__, 4, 0)))
+{
+ free(kdc_log_msg_va(context, config, level, fmt, ap));
+}
+
+KDC_LIB_FUNCTION void KDC_LIB_CALL
+kdc_log(krb5_context context,
+ krb5_kdc_configuration *config,
+ int level, const char *fmt, ...)
+ __attribute__ ((__format__ (__printf__, 4, 5)))
+{
+ va_list ap;
+ va_start(ap, fmt);
+ free(kdc_log_msg_va(context, config, level, fmt, ap));
+ va_end(ap);
+}
diff --git a/third_party/heimdal/kdc/main.c b/third_party/heimdal/kdc/main.c
new file mode 100644
index 0000000..a4a7ade
--- /dev/null
+++ b/third_party/heimdal/kdc/main.c
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 1997-2005 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "kdc_locl.h"
+#ifdef HAVE_UTIL_H
+#include <util.h>
+#endif
+
+#ifdef HAVE_CAPNG
+#include <cap-ng.h>
+#endif
+
+sig_atomic_t exit_flag = 0;
+
+int detach_from_console = -1;
+int daemon_child = -1;
+int do_bonjour = -1;
+
+static RETSIGTYPE
+sigchld(int sig)
+{
+}
+
+static RETSIGTYPE
+sigterm(int sig)
+{
+ exit_flag = sig;
+}
+
+/*
+ * Allow dropping root bit, since heimdal reopens the database all the
+ * time the database needs to be owned by the user you are switched
+ * too. A better solution is to split the kdc in to more processes and
+ * run the network facing part with very low privilege.
+ */
+
+static void
+switch_environment(void)
+{
+#ifdef HAVE_GETEUID
+ if ((runas_string || chroot_string) && geteuid() != 0)
+ errx(1, "no running as root, can't switch user/chroot");
+
+ if (chroot_string) {
+ if (chroot(chroot_string))
+ err(1, "chroot(%s) failed", chroot_string);
+ if (chdir("/"))
+ err(1, "chdir(/) after chroot failed");
+ }
+
+ if (runas_string) {
+ struct passwd *pw;
+
+ pw = getpwnam(runas_string);
+ if (pw == NULL)
+ errx(1, "unknown user %s", runas_string);
+
+ if (initgroups(pw->pw_name, pw->pw_gid) < 0)
+ err(1, "initgroups failed");
+
+#ifndef HAVE_CAPNG
+ if (setgid(pw->pw_gid) < 0)
+ err(1, "setgid(%s) failed", runas_string);
+
+ if (setuid(pw->pw_uid) < 0)
+ err(1, "setuid(%s)", runas_string);
+#else
+ capng_clear (CAPNG_EFFECTIVE | CAPNG_PERMITTED);
+ if (capng_updatev (CAPNG_ADD, CAPNG_EFFECTIVE | CAPNG_PERMITTED,
+ CAP_NET_BIND_SERVICE, CAP_SETPCAP, -1) < 0)
+ err(1, "capng_updateev");
+
+ if (capng_change_id(pw->pw_uid, pw->pw_gid,
+ CAPNG_CLEAR_BOUNDING) < 0)
+ err(1, "capng_change_id(%s)", runas_string);
+#endif
+ }
+#endif
+}
+
+int
+main(int argc, char **argv)
+{
+ krb5_error_code ret;
+ krb5_context context;
+ krb5_kdc_configuration *config;
+ int optidx = 0;
+
+ setprogname(argv[0]);
+
+ ret = krb5_init_context(&context);
+ if (ret == KRB5_CONFIG_BADFORMAT)
+ errx (1, "krb5_init_context failed to parse configuration file");
+ else if (ret)
+ errx (1, "krb5_init_context failed: %d", ret);
+
+ ret = krb5_kt_register(context, &hdb_get_kt_ops);
+ if (ret)
+ errx (1, "krb5_kt_register(HDB) failed: %d", ret);
+
+ config = configure(context, argc, argv, &optidx);
+
+#ifdef HAVE_SIGACTION
+ {
+ struct sigaction sa;
+
+ sa.sa_flags = 0;
+ sa.sa_handler = sigterm;
+ sigemptyset(&sa.sa_mask);
+
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGTERM, &sa, NULL);
+#ifdef SIGXCPU
+ sigaction(SIGXCPU, &sa, NULL);
+#endif
+
+#ifdef SIGCHLD
+ sa.sa_handler = sigchld;
+ sigaction(SIGCHLD, &sa, NULL);
+#endif
+
+ sa.sa_handler = SIG_IGN;
+#ifdef SIGPIPE
+ sigaction(SIGPIPE, &sa, NULL);
+#endif
+ }
+#else
+ signal(SIGINT, sigterm);
+ signal(SIGTERM, sigterm);
+#ifdef SIGCHLD
+ signal(SIGCHLD, sigchld);
+#endif
+#ifdef SIGXCPU
+ signal(SIGXCPU, sigterm);
+#endif
+#ifdef SIGPIPE
+ signal(SIGPIPE, SIG_IGN);
+#endif
+#endif
+ rk_pidfile(NULL);
+
+ switch_environment();
+
+ start_kdc(context, config, argv[0]);
+ _krb5_unload_plugins(context, "kdc");
+ krb5_free_context(context);
+ free(config);
+ return 0;
+}
diff --git a/third_party/heimdal/kdc/misc.c b/third_party/heimdal/kdc/misc.c
new file mode 100644
index 0000000..00bbe0c
--- /dev/null
+++ b/third_party/heimdal/kdc/misc.c
@@ -0,0 +1,363 @@
+/*
+ * Copyright (c) 1997 - 2001 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "kdc_locl.h"
+
+static int
+name_type_ok(krb5_context context,
+ krb5_kdc_configuration *config,
+ krb5_const_principal principal)
+{
+ int nt = krb5_principal_get_type(context, principal);
+
+ if (!krb5_principal_is_krbtgt(context, principal))
+ return 1;
+ if (nt == KRB5_NT_SRV_INST || nt == KRB5_NT_UNKNOWN)
+ return 1;
+ if (config->strict_nametypes == 0)
+ return 1;
+ return 0;
+}
+
+struct timeval _kdc_now;
+
+static krb5_error_code
+synthesize_hdb_close(krb5_context context, struct HDB *db)
+{
+ (void) context;
+ (void) db;
+ return 0;
+}
+
+/*
+ * Synthesize an HDB entry suitable for PKINIT and GSS preauth.
+ */
+static krb5_error_code
+synthesize_client(krb5_context context,
+ krb5_kdc_configuration *config,
+ krb5_const_principal princ,
+ HDB **db,
+ hdb_entry **h)
+{
+ static HDB null_db;
+ krb5_error_code ret;
+ hdb_entry *e;
+
+ /* Hope this works! */
+ null_db.hdb_destroy = synthesize_hdb_close;
+ null_db.hdb_close = synthesize_hdb_close;
+ if (db)
+ *db = &null_db;
+
+ ret = (e = calloc(1, sizeof(*e))) ? 0 : krb5_enomem(context);
+ if (ret == 0) {
+ e->flags.client = 1;
+ e->flags.immutable = 1;
+ e->flags.virtual = 1;
+ e->flags.synthetic = 1;
+ e->flags.do_not_store = 1;
+ e->kvno = 1;
+ e->keys.len = 0;
+ e->keys.val = NULL;
+ e->created_by.time = time(NULL);
+ e->modified_by = NULL;
+ e->valid_start = NULL;
+ e->valid_end = NULL;
+ e->pw_end = NULL;
+ e->etypes = NULL;
+ e->generation = NULL;
+ e->extensions = NULL;
+ }
+ if (ret == 0)
+ ret = (e->max_renew = calloc(1, sizeof(*e->max_renew))) ?
+ 0 : krb5_enomem(context);
+ if (ret == 0)
+ ret = (e->max_life = calloc(1, sizeof(*e->max_life))) ?
+ 0 : krb5_enomem(context);
+ if (ret == 0)
+ ret = krb5_copy_principal(context, princ, &e->principal);
+ if (ret == 0)
+ ret = krb5_copy_principal(context, princ, &e->created_by.principal);
+ if (ret == 0) {
+ /*
+ * We can't check OCSP in the TGS path, so we can't let tickets for
+ * synthetic principals live very long.
+ */
+ *(e->max_renew) = config->synthetic_clients_max_renew;
+ *(e->max_life) = config->synthetic_clients_max_life;
+ *h = e;
+ } else if (e) {
+ hdb_free_entry(context, &null_db, e);
+ }
+ return ret;
+}
+
+KDC_LIB_FUNCTION krb5_error_code KDC_LIB_CALL
+_kdc_db_fetch(krb5_context context,
+ krb5_kdc_configuration *config,
+ krb5_const_principal principal,
+ unsigned flags,
+ krb5uint32 *kvno_ptr,
+ HDB **db,
+ hdb_entry **h)
+{
+ hdb_entry *ent = NULL;
+ krb5_error_code ret = HDB_ERR_NOENTRY;
+ int i;
+ unsigned kvno = 0;
+ krb5_principal enterprise_principal = NULL;
+ krb5_const_principal princ;
+
+ *h = NULL;
+ if (db)
+ *db = NULL;
+
+ if (!name_type_ok(context, config, principal))
+ return HDB_ERR_NOENTRY;
+
+ flags |= HDB_F_DECRYPT;
+ if (kvno_ptr != NULL && *kvno_ptr != 0) {
+ kvno = *kvno_ptr;
+ flags |= HDB_F_KVNO_SPECIFIED;
+ } else {
+ flags |= HDB_F_ALL_KVNOS;
+ }
+
+ ent = calloc(1, sizeof (*ent));
+ if (ent == NULL)
+ return krb5_enomem(context);
+
+ if (principal->name.name_type == KRB5_NT_ENTERPRISE_PRINCIPAL) {
+ if (principal->name.name_string.len != 1) {
+ ret = KRB5_PARSE_MALFORMED;
+ krb5_set_error_message(context, ret,
+ "malformed request: "
+ "enterprise name with %d name components",
+ principal->name.name_string.len);
+ goto out;
+ }
+ ret = krb5_parse_name(context, principal->name.name_string.val[0],
+ &enterprise_principal);
+ if (ret)
+ goto out;
+ }
+
+ for (i = 0; i < config->num_db; i++) {
+ HDB *curdb = config->db[i];
+
+ if (db)
+ *db = curdb;
+
+ ret = curdb->hdb_open(context, curdb, O_RDONLY, 0);
+ if (ret) {
+ const char *msg = krb5_get_error_message(context, ret);
+ kdc_log(context, config, 0, "Failed to open database: %s", msg);
+ krb5_free_error_message(context, msg);
+ continue;
+ }
+
+ princ = principal;
+ if (!(curdb->hdb_capability_flags & HDB_CAP_F_HANDLE_ENTERPRISE_PRINCIPAL) && enterprise_principal)
+ princ = enterprise_principal;
+
+ ret = hdb_fetch_kvno(context, curdb, princ, flags, 0, 0, kvno, ent);
+ curdb->hdb_close(context, curdb);
+
+ if (ret == HDB_ERR_NOENTRY)
+ continue; /* Check the other databases */
+
+ /*
+ * This is really important, because errors like
+ * HDB_ERR_NOT_FOUND_HERE (used to indicate to Samba that
+ * the RODC on which this code is running does not have
+ * the key we need, and so a proxy to the KDC is required)
+ * have specific meaning, and need to be propogated up.
+ */
+ break;
+ }
+
+ switch (ret) {
+ case HDB_ERR_WRONG_REALM:
+ case 0:
+ /*
+ * the ent->entry.principal just contains hints for the client
+ * to retry. This is important for enterprise principal routing
+ * between trusts.
+ */
+ *h = ent;
+ ent = NULL;
+ break;
+
+ case HDB_ERR_NOENTRY:
+ if (db)
+ *db = NULL;
+ if ((flags & HDB_F_GET_CLIENT) && (flags & HDB_F_SYNTHETIC_OK) &&
+ config->synthetic_clients) {
+ ret = synthesize_client(context, config, principal, db, h);
+ if (ret) {
+ krb5_set_error_message(context, ret, "could not synthesize "
+ "HDB client principal entry");
+ ret = HDB_ERR_NOENTRY;
+ krb5_prepend_error_message(context, ret, "no such entry found in hdb");
+ }
+ } else {
+ krb5_set_error_message(context, ret, "no such entry found in hdb");
+ }
+ break;
+
+ default:
+ if (db)
+ *db = NULL;
+ break;
+ }
+
+out:
+ krb5_free_principal(context, enterprise_principal);
+ free(ent);
+ return ret;
+}
+
+KDC_LIB_FUNCTION void KDC_LIB_CALL
+_kdc_free_ent(krb5_context context, HDB *db, hdb_entry *ent)
+{
+ hdb_free_entry (context, db, ent);
+ free (ent);
+}
+
+/*
+ * Use the order list of preferred encryption types and sort the
+ * available keys and return the most preferred key.
+ */
+
+krb5_error_code
+_kdc_get_preferred_key(krb5_context context,
+ krb5_kdc_configuration *config,
+ hdb_entry *h,
+ const char *name,
+ krb5_enctype *enctype,
+ Key **key)
+{
+ krb5_error_code ret;
+ int i;
+
+ if (config->use_strongest_server_key) {
+ const krb5_enctype *p = krb5_kerberos_enctypes(context);
+
+ for (i = 0; p[i] != ETYPE_NULL; i++) {
+ if (krb5_enctype_valid(context, p[i]) != 0 &&
+ !_kdc_is_weak_exception(h->principal, p[i]))
+ continue;
+ ret = hdb_enctype2key(context, h, NULL, p[i], key);
+ if (ret != 0)
+ continue;
+ if (enctype != NULL)
+ *enctype = p[i];
+ return 0;
+ }
+ } else {
+ *key = NULL;
+
+ for (i = 0; i < h->keys.len; i++) {
+ if (krb5_enctype_valid(context, h->keys.val[i].key.keytype) != 0 &&
+ !_kdc_is_weak_exception(h->principal, h->keys.val[i].key.keytype))
+ continue;
+ ret = hdb_enctype2key(context, h, NULL,
+ h->keys.val[i].key.keytype, key);
+ if (ret != 0)
+ continue;
+ if (enctype != NULL)
+ *enctype = (*key)->key.keytype;
+ return 0;
+ }
+ }
+
+ krb5_set_error_message(context, ret = KRB5KDC_ERR_ETYPE_NOSUPP,
+ "No valid kerberos key found for %s", name);
+ return ret;
+}
+
+krb5_error_code
+_kdc_verify_checksum(krb5_context context,
+ krb5_crypto crypto,
+ krb5_key_usage usage,
+ const krb5_data *data,
+ Checksum *cksum)
+{
+ krb5_error_code ret;
+
+ ret = krb5_verify_checksum(context, crypto, usage,
+ data->data, data->length,
+ cksum);
+ if (ret == KRB5_PROG_SUMTYPE_NOSUPP)
+ ret = KRB5KDC_ERR_SUMTYPE_NOSUPP;
+
+ return ret;
+}
+
+/*
+ * Returns TRUE if a PAC should be included in ticket authorization data.
+ *
+ * Per [MS-KILE] 3.3.5.3, PACs are always included for TGTs; for service
+ * tickets, policy is governed by whether the client explicitly requested
+ * a PAC be omitted when requesting a TGT, or if the no-auth-data-reqd
+ * flag is set on the service principal entry.
+ *
+ * However, when issuing a cross-realm TGT to an AD realm our PAC might not
+ * interoperate correctly. Therefore we honor the no-auth-data-reqd HDB entry
+ * flag on cross-realm TGTs.
+ */
+
+krb5_boolean
+_kdc_include_pac_p(astgs_request_t r)
+{
+ return TRUE;
+}
+
+/*
+ * Notify the HDB backend and KDC plugin of the audited event.
+ */
+
+krb5_error_code
+_kdc_audit_request(astgs_request_t r)
+{
+ krb5_error_code ret;
+ struct HDB *hdb;
+
+ ret = _kdc_plugin_audit(r);
+ if (ret == 0 &&
+ (hdb = r->clientdb ? r->clientdb : r->config->db[0]) &&
+ hdb->hdb_audit)
+ ret = hdb->hdb_audit(r->context, hdb, r->client, (hdb_request_t)r);
+
+ return ret;
+}
diff --git a/third_party/heimdal/kdc/mit_dump.c b/third_party/heimdal/kdc/mit_dump.c
new file mode 100644
index 0000000..af380bb
--- /dev/null
+++ b/third_party/heimdal/kdc/mit_dump.c
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2000 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "hprop.h"
+
+extern krb5_error_code _hdb_mdb_value2entry(krb5_context context,
+ krb5_data *data,
+ krb5_kvno target_kvno,
+ hdb_entry *entry);
+
+extern int _hdb_mit_dump2mitdb_entry(krb5_context context,
+ char *line,
+ krb5_storage *sp);
+
+
+
+/*
+can have any number of princ stanzas.
+format is as follows (only \n indicates newlines)
+princ\t%d\t (%d is KRB5_KDB_V1_BASE_LENGTH, always 38)
+%d\t (strlen of principal e.g. shadow/foo@ANDREW.CMU.EDU)
+%d\t (number of tl_data)
+%d\t (number of key data, e.g. how many keys for this user)
+%d\t (extra data length)
+%s\t (principal name)
+%d\t (attributes)
+%d\t (max lifetime, seconds)
+%d\t (max renewable life, seconds)
+%d\t (expiration, seconds since epoch or 2145830400 for never)
+%d\t (password expiration, seconds, 0 for never)
+%d\t (last successful auth, seconds since epoch)
+%d\t (last failed auth, per above)
+%d\t (failed auth count)
+foreach tl_data 0 to number of tl_data - 1 as above
+ %d\t%d\t (data type, data length)
+ foreach tl_data 0 to length-1
+ %02x (tl data contents[element n])
+ except if tl_data length is 0
+ %d (always -1)
+ \t
+foreach key 0 to number of keys - 1 as above
+ %d\t%d\t (key data version, kvno)
+ foreach version 0 to key data version - 1 (a key or a salt)
+ %d\t%d\t(data type for this key, data length for this key)
+ foreach key data length 0 to length-1
+ %02x (key data contents[element n])
+ except if key_data length is 0
+ %d (always -1)
+ \t
+foreach extra data length 0 to length - 1
+ %02x (extra data part)
+unless no extra data
+ %d (always -1)
+;\n
+
+*/
+
+static char *
+nexttoken(char **p)
+{
+ char *q;
+ do {
+ q = strsep(p, " \t");
+ } while(q && *q == '\0');
+ return q;
+}
+
+#include <kadm5/admin.h>
+
+/* XXX: Principal names with '\n' cannot be dumped or loaded */
+static int
+my_fgetln(FILE *f, char **bufp, size_t *szp, size_t *lenp)
+{
+ size_t len;
+ size_t sz = *szp;
+ char *buf = *bufp;
+ char *n;
+
+ if (!buf) {
+ buf = malloc(sz ? sz : 8192);
+ if (!buf)
+ return ENOMEM;
+ if (!sz)
+ sz = 8192;
+ }
+
+ len = 0;
+ while (fgets(&buf[len], sz-len, f) != NULL) {
+ len += strlen(&buf[len]);
+ if (buf[len-1] == '\n')
+ break;
+ if (feof(f))
+ break;
+ if (sz > SIZE_MAX/2 ||
+ (n = realloc(buf, sz += 1 + (sz >> 1))) == NULL) {
+ free(buf);
+ *bufp = NULL;
+ *szp = 0;
+ *lenp = 0;
+ return ENOMEM;
+ }
+ buf = n;
+ }
+ *bufp = buf;
+ *szp = sz;
+ *lenp = len;
+ return 0; /* *len == 0 || no EOL -> EOF */
+}
+
+int
+mit_prop_dump(void *arg, const char *file)
+{
+ krb5_error_code ret;
+ size_t line_bufsz = 0;
+ size_t line_len = 0;
+ char *line = NULL;
+ int lineno = 0;
+ FILE *f;
+ hdb_entry ent;
+ struct prop_data *pd = arg;
+ krb5_storage *sp = NULL;
+ krb5_data kdb_ent;
+
+ memset(&ent, 0, sizeof (ent));
+ f = fopen(file, "r");
+ if (f == NULL)
+ return errno;
+
+ ret = ENOMEM;
+ sp = krb5_storage_emem();
+ if (!sp)
+ goto out;
+ while ((ret = my_fgetln(f, &line, &line_bufsz, &line_len)) == 0 &&
+ line_len > 0) {
+ char *p = line;
+ char *q;
+
+ lineno++;
+ if(strncmp(line, "kdb5_util", strlen("kdb5_util")) == 0) {
+ int major;
+ q = nexttoken(&p);
+ if (strcmp(q, "kdb5_util") != 0)
+ errx(1, "line %d: unknown version", lineno);
+ q = nexttoken(&p); /* load_dump */
+ if (strcmp(q, "load_dump") != 0)
+ errx(1, "line %d: unknown version", lineno);
+ q = nexttoken(&p); /* load_dump */
+ if (strcmp(q, "version") != 0)
+ errx(1, "line %d: unknown version", lineno);
+ q = nexttoken(&p); /* x.0 */
+ if (sscanf(q, "%d", &major) != 1)
+ errx(1, "line %d: unknown version", lineno);
+ if (major != 4 && major != 5 && major != 6)
+ errx(1, "unknown dump file format, got %d, expected 4-6",
+ major);
+ continue;
+ } else if(strncmp(p, "policy", strlen("policy")) == 0) {
+ warnx("line: %d: ignoring policy (not supported)", lineno);
+ continue;
+ } else if(strncmp(p, "princ", strlen("princ")) != 0) {
+ warnx("line %d: not a principal", lineno);
+ continue;
+ }
+ krb5_storage_truncate(sp, 0);
+ ret = _hdb_mit_dump2mitdb_entry(pd->context, line, sp);
+ if (ret) {
+ if (ret > 0)
+ warn("line: %d: failed to parse; ignoring", lineno);
+ else
+ warnx("line: %d: failed to parse; ignoring", lineno);
+ continue;
+ }
+ ret = krb5_storage_to_data(sp, &kdb_ent);
+ if (ret) break;
+ ret = _hdb_mdb_value2entry(pd->context, &kdb_ent, 0, &ent);
+ krb5_data_free(&kdb_ent);
+ if (ret) {
+ warnx("line: %d: failed to store; ignoring", lineno);
+ continue;
+ }
+ ret = v5_prop(pd->context, NULL, &ent, arg);
+ hdb_free_entry(pd->context, NULL, &ent); /* XXX */
+ if (ret) break;
+ }
+
+out:
+ fclose(f);
+ free(line);
+ if (sp)
+ krb5_storage_free(sp);
+ if (ret && ret == ENOMEM)
+ errx(1, "out of memory");
+ if (ret)
+ errx(1, "line %d: problem parsing dump line", lineno);
+ return ret;
+}
+
diff --git a/third_party/heimdal/kdc/mssfu.c b/third_party/heimdal/kdc/mssfu.c
new file mode 100644
index 0000000..471e193
--- /dev/null
+++ b/third_party/heimdal/kdc/mssfu.c
@@ -0,0 +1,693 @@
+/*
+ * Copyright (c) 1997-2008 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "kdc_locl.h"
+
+/*
+ * [MS-SFU] Kerberos Protocol Extensions:
+ * Service for User (S4U2Self) and Constrained Delegation Protocol (S4U2Proxy)
+ * https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/
+ */
+
+/*
+ * Determine if constrained delegation is allowed from this client to this server
+ */
+
+static krb5_error_code
+check_constrained_delegation(krb5_context context,
+ krb5_kdc_configuration *config,
+ HDB *clientdb,
+ hdb_entry *client,
+ hdb_entry *server,
+ krb5_const_principal target)
+{
+ const HDB_Ext_Constrained_delegation_acl *acl;
+ krb5_error_code ret;
+ size_t i;
+
+ /*
+ * constrained delegation (S4U2Proxy) only works within
+ * the same realm. We use the already canonicalized version
+ * of the principals here, while "target" is the principal
+ * provided by the client.
+ */
+ if (!krb5_realm_compare(context, client->principal, server->principal)) {
+ ret = KRB5KDC_ERR_BADOPTION;
+ kdc_log(context, config, 4,
+ "Bad request for constrained delegation");
+ return ret;
+ }
+
+ if (clientdb->hdb_check_constrained_delegation) {
+ ret = clientdb->hdb_check_constrained_delegation(context, clientdb, client, target);
+ if (ret == 0)
+ return 0;
+ } else {
+ /* if client delegates to itself, that ok */
+ if (krb5_principal_compare(context, client->principal, server->principal) == TRUE)
+ return 0;
+
+ ret = hdb_entry_get_ConstrainedDelegACL(client, &acl);
+ if (ret) {
+ krb5_clear_error_message(context);
+ return ret;
+ }
+
+ if (acl) {
+ for (i = 0; i < acl->len; i++) {
+ if (krb5_principal_compare(context, target, &acl->val[i]) == TRUE)
+ return 0;
+ }
+ }
+ ret = KRB5KDC_ERR_BADOPTION;
+ }
+ kdc_log(context, config, 4,
+ "Bad request for constrained delegation");
+ return ret;
+}
+
+/*
+ * Determine if resource-based constrained delegation is allowed from this
+ * client to this server
+ */
+
+static krb5_error_code
+check_rbcd(krb5_context context,
+ krb5_kdc_configuration *config,
+ HDB *clientdb,
+ krb5_const_principal s4u_principal,
+ const hdb_entry *client_krbtgt,
+ const hdb_entry *client,
+ const hdb_entry *device_krbtgt,
+ const hdb_entry *device,
+ krb5_const_pac client_pac,
+ krb5_const_pac device_pac,
+ const hdb_entry *target)
+{
+ krb5_error_code ret = KRB5KDC_ERR_BADOPTION;
+
+ if (clientdb->hdb_check_rbcd) {
+ ret = clientdb->hdb_check_rbcd(context,
+ clientdb,
+ client_krbtgt,
+ client,
+ device_krbtgt,
+ device,
+ s4u_principal,
+ client_pac,
+ device_pac,
+ target);
+ if (ret == 0)
+ return 0;
+ }
+
+ kdc_log(context, config, 4,
+ "Bad request for resource-based constrained delegation");
+ return ret;
+}
+
+/*
+ * Validate a protocol transition (S4U2Self) request. If successfully
+ * validated then the client in the request structure will be replaced
+ * with the impersonated client.
+ */
+
+krb5_error_code
+_kdc_validate_protocol_transition(astgs_request_t r, const PA_DATA *for_user)
+{
+ krb5_error_code ret;
+ KDC_REQ_BODY *b = &r->req.req_body;
+ EncTicketPart *ticket = &r->ticket->ticket;
+ hdb_entry *s4u_client = NULL;
+ HDB *s4u_clientdb;
+ int flags = HDB_F_FOR_TGS_REQ;
+ krb5_principal s4u_client_name = NULL, s4u_canon_client_name = NULL;
+ krb5_pac s4u_pac = NULL;
+ char *s4ucname = NULL;
+ krb5_crypto crypto;
+ krb5_data datack;
+ PA_S4U2Self self;
+ const char *str;
+
+ heim_assert(r->client != NULL, "client must be non-NULL");
+
+ memset(&self, 0, sizeof(self));
+
+ if (b->kdc_options.canonicalize)
+ flags |= HDB_F_CANON;
+
+ ret = decode_PA_S4U2Self(for_user->padata_value.data,
+ for_user->padata_value.length,
+ &self, NULL);
+ if (ret) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "Failed to decode PA-S4U2Self");
+ kdc_log(r->context, r->config, 4, "Failed to decode PA-S4U2Self");
+ goto out;
+ }
+
+ if (!krb5_checksum_is_keyed(r->context, self.cksum.cksumtype)) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "PA-S4U2Self with unkeyed checksum");
+ kdc_log(r->context, r->config, 4, "Reject PA-S4U2Self with unkeyed checksum");
+ ret = KRB5KRB_AP_ERR_INAPP_CKSUM;
+ goto out;
+ }
+
+ ret = _krb5_s4u2self_to_checksumdata(r->context, &self, &datack);
+ if (ret)
+ goto out;
+
+ ret = krb5_crypto_init(r->context, &ticket->key, 0, &crypto);
+ if (ret) {
+ const char *msg = krb5_get_error_message(r->context, ret);
+ krb5_data_free(&datack);
+ kdc_log(r->context, r->config, 4, "krb5_crypto_init failed: %s", msg);
+ krb5_free_error_message(r->context, msg);
+ goto out;
+ }
+
+ /* Allow HMAC_MD5 checksum with any key type */
+ if (self.cksum.cksumtype == CKSUMTYPE_HMAC_MD5) {
+ struct krb5_crypto_iov iov;
+ unsigned char csdata[16];
+ Checksum cs;
+
+ cs.checksum.length = sizeof(csdata);
+ cs.checksum.data = &csdata;
+
+ iov.data.data = datack.data;
+ iov.data.length = datack.length;
+ iov.flags = KRB5_CRYPTO_TYPE_DATA;
+
+ ret = _krb5_HMAC_MD5_checksum(r->context, NULL, &crypto->key,
+ KRB5_KU_OTHER_CKSUM, &iov, 1,
+ &cs);
+ if (ret == 0 &&
+ krb5_data_ct_cmp(&cs.checksum, &self.cksum.checksum) != 0)
+ ret = KRB5KRB_AP_ERR_BAD_INTEGRITY;
+ } else {
+ ret = _kdc_verify_checksum(r->context,
+ crypto,
+ KRB5_KU_OTHER_CKSUM,
+ &datack,
+ &self.cksum);
+ }
+ krb5_data_free(&datack);
+ krb5_crypto_destroy(r->context, crypto);
+ if (ret) {
+ const char *msg = krb5_get_error_message(r->context, ret);
+ kdc_audit_addreason((kdc_request_t)r,
+ "S4U2Self checksum failed");
+ kdc_log(r->context, r->config, 4,
+ "krb5_verify_checksum failed for S4U2Self: %s", msg);
+ krb5_free_error_message(r->context, msg);
+ goto out;
+ }
+
+ ret = _krb5_principalname2krb5_principal(r->context,
+ &s4u_client_name,
+ self.name,
+ self.realm);
+ if (ret)
+ goto out;
+
+ ret = krb5_unparse_name(r->context, s4u_client_name, &s4ucname);
+ if (ret)
+ goto out;
+
+ /*
+ * Note no HDB_F_SYNTHETIC_OK -- impersonating non-existent clients
+ * is probably not desirable!
+ */
+ ret = _kdc_db_fetch(r->context, r->config, s4u_client_name,
+ HDB_F_GET_CLIENT | flags, NULL,
+ &s4u_clientdb, &s4u_client);
+ if (ret) {
+ const char *msg;
+
+ /*
+ * If the client belongs to the same realm as our krbtgt, it
+ * should exist in the local database.
+ *
+ */
+ if (ret == HDB_ERR_NOENTRY)
+ ret = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
+ msg = krb5_get_error_message(r->context, ret);
+ kdc_audit_addreason((kdc_request_t)r,
+ "S4U2Self principal to impersonate not found");
+ kdc_log(r->context, r->config, 2,
+ "S4U2Self principal to impersonate %s not found in database: %s",
+ s4ucname, msg);
+ krb5_free_error_message(r->context, msg);
+ goto out;
+ }
+
+ /*
+ * Ignore require_pwchange and pw_end attributes (as Windows does),
+ * since S4U2Self is not password authentication.
+ */
+ s4u_client->flags.require_pwchange = FALSE;
+ free(s4u_client->pw_end);
+ s4u_client->pw_end = NULL;
+
+ ret = kdc_check_flags(r, FALSE, s4u_client, r->server);
+ if (ret)
+ goto out; /* kdc_check_flags() calls kdc_audit_addreason() */
+
+ ret = _kdc_pac_generate(r,
+ s4u_client,
+ r->server,
+ NULL,
+ KRB5_PAC_WAS_GIVEN_IMPLICITLY,
+ &s4u_pac);
+ if (ret) {
+ kdc_log(r->context, r->config, 4, "PAC generation failed for -- %s", s4ucname);
+ goto out;
+ }
+
+ /*
+ * Check that service doing the impersonating is
+ * requesting a ticket to it-self.
+ */
+ ret = _kdc_check_client_matches_target_service(r->context,
+ r->config,
+ r->clientdb,
+ r->client,
+ r->server,
+ r->server_princ);
+ if (ret) {
+ kdc_log(r->context, r->config, 4, "S4U2Self: %s is not allowed "
+ "to impersonate to service "
+ "(tried for user %s to service %s)",
+ r->cname, s4ucname, r->sname);
+ goto out;
+ }
+
+ ret = krb5_copy_principal(r->context, s4u_client->principal,
+ &s4u_canon_client_name);
+ if (ret)
+ goto out;
+
+ /*
+ * If the service isn't trusted for authentication to
+ * delegation or if the impersonate client is disallowed
+ * forwardable, remove the forwardable flag.
+ */
+ if (r->client->flags.trusted_for_delegation &&
+ s4u_client->flags.forwardable) {
+ str = " [forwardable]";
+ } else {
+ b->kdc_options.forwardable = 0;
+ str = "";
+ }
+ kdc_log(r->context, r->config, 4, "s4u2self %s impersonating %s to "
+ "service %s%s", r->cname, s4ucname, r->sname, str);
+
+ /*
+ * Replace all client information in the request with the
+ * impersonated client. (The audit entry containing the original
+ * client name will have been created before this point.)
+ */
+ _kdc_request_set_cname_nocopy((kdc_request_t)r, &s4ucname);
+ _kdc_request_set_client_princ_nocopy(r, &s4u_client_name);
+
+ _kdc_free_ent(r->context, r->clientdb, r->client);
+ r->client = s4u_client;
+ s4u_client = NULL;
+ r->clientdb = s4u_clientdb;
+ s4u_clientdb = NULL;
+
+ _kdc_request_set_canon_client_princ_nocopy(r, &s4u_canon_client_name);
+ _kdc_request_set_pac_nocopy(r, &s4u_pac);
+
+out:
+ if (s4u_client)
+ _kdc_free_ent(r->context, s4u_clientdb, s4u_client);
+ krb5_free_principal(r->context, s4u_client_name);
+ krb5_xfree(s4ucname);
+ krb5_free_principal(r->context, s4u_canon_client_name);
+ krb5_pac_free(r->context, s4u_pac);
+
+ free_PA_S4U2Self(&self);
+
+ return ret;
+}
+
+/*
+ * Validate a constrained delegation (S4U2Proxy) request. If
+ * successfully validated then the client in the request structure will
+ * be replaced with the client from the evidence ticket.
+ */
+
+krb5_error_code
+_kdc_validate_constrained_delegation(astgs_request_t r)
+{
+ krb5_error_code ret;
+ KDC_REQ_BODY *b = &r->req.req_body;
+ int flags = HDB_F_FOR_TGS_REQ;
+ krb5_principal s4u_client_name = NULL, s4u_server_name = NULL;
+ krb5_principal s4u_canon_client_name = NULL;
+ krb5_pac s4u_pac = NULL;
+ uint64_t s4u_pac_attributes;
+ char *s4ucname = NULL, *s4usname = NULL;
+ EncTicketPart evidence_tkt;
+ HDB *s4u_clientdb;
+ hdb_entry *s4u_client = NULL;
+ HDB *s4u_serverdb = NULL;
+ hdb_entry *s4u_server = NULL;
+ krb5_boolean ad_kdc_issued = FALSE;
+ Key *clientkey;
+ Ticket *t;
+ krb5_const_realm local_realm;
+ const PA_DATA *pac_options_data = NULL;
+ int pac_options_data_idx = 0;
+ krb5_boolean rbcd_support = FALSE;
+
+ memset(&evidence_tkt, 0, sizeof(evidence_tkt));
+ local_realm =
+ krb5_principal_get_comp_string(r->context, r->krbtgt->principal, 1);
+
+ /*
+ * We require that the service's TGT has a PAC; this will have been
+ * validated prior to this function being called.
+ */
+ if (r->pac == NULL) {
+ ret = KRB5KDC_ERR_BADOPTION;
+ kdc_audit_addreason((kdc_request_t)r, "Missing PAC");
+ kdc_log(r->context, r->config, 4,
+ "Constrained delegation without PAC, %s/%s",
+ r->cname, r->sname);
+ goto out;
+ }
+
+ t = &b->additional_tickets->val[0];
+
+ ret = _krb5_principalname2krb5_principal(r->context,
+ &s4u_server_name,
+ t->sname,
+ t->realm);
+ if (ret)
+ goto out;
+
+ ret = krb5_unparse_name(r->context, s4u_server_name, &s4usname);
+ if (ret)
+ goto out;
+
+ /*
+ * Look up the name given in the ticket in the database. We don’t ask for
+ * canonicalisation, so that we get back the same principal that was
+ * specified in the ticket.
+ */
+ ret = _kdc_db_fetch(r->context, r->config, s4u_server_name,
+ HDB_F_GET_SERVER | HDB_F_DELAY_NEW_KEYS | flags,
+ NULL, &s4u_serverdb, &s4u_server);
+ if (ret == HDB_ERR_NOENTRY)
+ ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
+ if (ret) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "Constrained delegation service principal unknown");
+ goto out;
+ }
+
+ /*
+ * Check that the delegating server (r->client) is the same one as specified
+ * in the ticket. This is to make sure that the server hasn’t forged the
+ * sname, which is in the unencrypted part of the ticket.
+ */
+ ret = _kdc_check_client_matches_target_service(r->context,
+ r->config,
+ s4u_serverdb,
+ s4u_server,
+ r->client,
+ r->client_princ);
+ if (ret == KRB5KRB_AP_ERR_BADMATCH)
+ ret = KRB5KDC_ERR_BADOPTION;
+ if (ret)
+ goto out;
+
+ ret = hdb_enctype2key(r->context, r->client,
+ hdb_kvno2keys(r->context, r->client,
+ t->enc_part.kvno ? * t->enc_part.kvno : 0),
+ t->enc_part.etype, &clientkey);
+ if (ret) {
+ ret = KRB5KDC_ERR_ETYPE_NOSUPP; /* XXX */
+ goto out;
+ }
+
+ ret = krb5_decrypt_ticket(r->context, t, &clientkey->key, &evidence_tkt, 0);
+ if (ret) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "Failed to decrypt constrained delegation ticket");
+ kdc_log(r->context, r->config, 4,
+ "failed to decrypt ticket for "
+ "constrained delegation from %s to %s", r->cname, r->sname);
+ goto out;
+ }
+
+ ret = _krb5_principalname2krb5_principal(r->context,
+ &s4u_client_name,
+ evidence_tkt.cname,
+ evidence_tkt.crealm);
+ if (ret)
+ goto out;
+
+ ret = krb5_unparse_name(r->context, s4u_client_name, &s4ucname);
+ if (ret)
+ goto out;
+
+ kdc_audit_addkv((kdc_request_t)r, 0, "impersonatee", "%s", s4ucname);
+
+ /* check that ticket is valid */
+ if (evidence_tkt.flags.forwardable == 0) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "Missing forwardable flag on ticket for constrained delegation");
+ kdc_log(r->context, r->config, 4,
+ "Missing forwardable flag on ticket for "
+ "constrained delegation from %s (%s) as %s to %s ",
+ r->cname, s4usname, s4ucname, r->sname);
+ ret = KRB5KDC_ERR_BADOPTION;
+ goto out;
+ }
+
+ pac_options_data = _kdc_find_padata(&r->req,
+ &pac_options_data_idx,
+ KRB5_PADATA_PAC_OPTIONS);
+ if (pac_options_data != NULL) {
+ PA_PAC_OPTIONS pac_options;
+ size_t size = 0;
+
+ ret = decode_PA_PAC_OPTIONS(pac_options_data->padata_value.data,
+ pac_options_data->padata_value.length,
+ &pac_options,
+ &size);
+ if (ret) {
+ goto out;
+ }
+
+ if (size != pac_options_data->padata_value.length) {
+ free_PA_PAC_OPTIONS(&pac_options);
+ ret = KRB5KDC_ERR_BADOPTION;
+ goto out;
+ }
+
+ rbcd_support = pac_options.flags.resource_based_constrained_delegation != 0;
+
+ free_PA_PAC_OPTIONS(&pac_options);
+ }
+
+ if (rbcd_support) {
+ ret = check_rbcd(r->context, r->config, r->clientdb,
+ s4u_client_name,
+ r->krbtgt, r->client,
+ r->armor_server, r->armor_client,
+ r->pac, r->armor_pac,
+ r->server);
+ } else {
+ ret = KRB5KDC_ERR_BADOPTION;
+ }
+ if (ret == KRB5KDC_ERR_BADOPTION) {
+ /* RBCD was denied or not supported; try constrained delegation. */
+ ret = check_constrained_delegation(r->context, r->config, r->clientdb,
+ r->client, r->server, r->server_princ);
+ if (ret) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "Constrained delegation not allowed");
+ kdc_log(r->context, r->config, 4,
+ "constrained delegation from %s (%s) as %s to %s not allowed",
+ r->cname, s4usname, s4ucname, r->sname);
+ goto out;
+ }
+ } else if (ret) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "Resource-based constrained delegation not allowed");
+ kdc_log(r->context, r->config, 4,
+ "resource-based constrained delegation from %s (%s) as %s to %s not allowed",
+ r->cname, s4usname, s4ucname, r->sname);
+ goto out;
+ }
+
+ ret = _kdc_verify_flags(r->context, r->config, &evidence_tkt, s4ucname);
+ if (ret) {
+ kdc_audit_addreason((kdc_request_t)r,
+ "Constrained delegation ticket expired or invalid");
+ goto out;
+ }
+
+ /* Try lookup the delegated client in DB */
+ ret = _kdc_db_fetch_client(r->context, r->config, flags,
+ s4u_client_name, s4ucname, local_realm,
+ &s4u_clientdb, &s4u_client);
+ if (ret)
+ goto out;
+
+ if (s4u_client != NULL) {
+ ret = kdc_check_flags(r, FALSE, s4u_client, r->server);
+ if (ret)
+ goto out;
+ }
+
+ /*
+ * TODO: pass in t->sname and t->realm and build
+ * a S4U_DELEGATION_INFO blob to the PAC.
+ */
+ ret = _kdc_check_pac(r, s4u_client_name, s4u_server,
+ s4u_client, r->server, r->krbtgt, r->client,
+ &clientkey->key, &r->ticket_key->key, &evidence_tkt,
+ &ad_kdc_issued, &s4u_pac,
+ &s4u_canon_client_name, &s4u_pac_attributes);
+ if (ret) {
+ const char *msg = krb5_get_error_message(r->context, ret);
+ kdc_audit_addreason((kdc_request_t)r,
+ "Constrained delegation ticket PAC check failed");
+ kdc_log(r->context, r->config, 4,
+ "Verify delegated PAC failed to %s for client "
+ "%s (%s) as %s from %s with %s",
+ r->sname, r->cname, s4usname, s4ucname, r->from, msg);
+ krb5_free_error_message(r->context, msg);
+ goto out;
+ }
+
+ if (s4u_pac == NULL || !ad_kdc_issued) {
+ ret = KRB5KDC_ERR_BADOPTION;
+ kdc_log(r->context, r->config, 4,
+ "Ticket not signed with PAC; service %s failed for "
+ "for delegation to %s for client %s (%s) from %s; (%s).",
+ r->sname, s4ucname, s4usname, r->cname, r->from,
+ s4u_pac ? "Ticket unsigned" : "No PAC");
+ kdc_audit_addreason((kdc_request_t)r,
+ "Constrained delegation ticket not signed");
+ goto out;
+ }
+
+ heim_assert(s4u_pac != NULL, "ad_kdc_issued implies the PAC is non-NULL");
+
+ ret = _kdc_pac_update(r, s4u_client_name, s4u_server, r->pac,
+ s4u_client, r->server, r->krbtgt,
+ &s4u_pac);
+ if (ret == KRB5_PLUGIN_NO_HANDLE) {
+ ret = 0;
+ }
+ if (ret) {
+ const char *msg = krb5_get_error_message(r->context, ret);
+ kdc_audit_addreason((kdc_request_t)r,
+ "Constrained delegation ticket PAC update failed");
+ kdc_log(r->context, r->config, 4,
+ "Update delegated PAC failed to %s for client "
+ "%s (%s) as %s from %s with %s",
+ r->sname, r->cname, s4usname, s4ucname, r->from, msg);
+ krb5_free_error_message(r->context, msg);
+ goto out;
+ }
+
+ /*
+ * If the evidence ticket PAC didn't include PAC_UPN_DNS_INFO with
+ * the canonical client name, but the user is local to our KDC, we
+ * can insert the canonical client name ourselves.
+ */
+ if (s4u_canon_client_name == NULL && s4u_client != NULL) {
+ ret = krb5_copy_principal(r->context, s4u_client->principal,
+ &s4u_canon_client_name);
+ if (ret)
+ goto out;
+ }
+
+ if (b->enc_authorization_data && r->rk_is_subkey == 0) {
+ krb5_free_keyblock_contents(r->context, &r->enc_ad_key);
+ ret = krb5_copy_keyblock_contents(r->context,
+ &evidence_tkt.key,
+ &r->enc_ad_key);
+ if (ret)
+ goto out;
+ }
+
+ kdc_log(r->context, r->config, 4, "constrained delegation for %s "
+ "from %s (%s) to %s", s4ucname, r->cname, s4usname, r->sname);
+
+ /*
+ * Replace all client information in the request with the
+ * impersonated client. (The audit entry containing the original
+ * client name will have been created before this point.)
+ */
+ _kdc_request_set_cname_nocopy((kdc_request_t)r, &s4ucname);
+ _kdc_request_set_client_princ_nocopy(r, &s4u_client_name);
+
+ _kdc_free_ent(r->context, r->clientdb, r->client);
+ r->client = s4u_client;
+ s4u_client = NULL;
+ r->clientdb = s4u_clientdb;
+ s4u_clientdb = NULL;
+
+ _kdc_request_set_canon_client_princ_nocopy(r, &s4u_canon_client_name);
+ _kdc_request_set_pac_nocopy(r, &s4u_pac);
+
+ r->pac_attributes = s4u_pac_attributes;
+
+ r->et.authtime = evidence_tkt.authtime;
+
+out:
+ if (s4u_client)
+ _kdc_free_ent(r->context, s4u_clientdb, s4u_client);
+ if (s4u_server)
+ _kdc_free_ent(r->context, s4u_serverdb, s4u_server);
+ krb5_free_principal(r->context, s4u_client_name);
+ krb5_xfree(s4ucname);
+ krb5_free_principal(r->context, s4u_server_name);
+ krb5_xfree(s4usname);
+ krb5_free_principal(r->context, s4u_canon_client_name);
+ krb5_pac_free(r->context, s4u_pac);
+
+ free_EncTicketPart(&evidence_tkt);
+
+ return ret;
+}
diff --git a/third_party/heimdal/kdc/negotiate_token_validator.c b/third_party/heimdal/kdc/negotiate_token_validator.c
new file mode 100644
index 0000000..20250c6
--- /dev/null
+++ b/third_party/heimdal/kdc/negotiate_token_validator.c
@@ -0,0 +1,323 @@
+/*
+ * Copyright (c) 2019 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This is a plugin by which bx509d can validate Negotiate tokens.
+ *
+ * [kdc]
+ * negotiate_token_validator = {
+ * keytab = ...
+ * }
+ */
+
+#define _DEFAULT_SOURCE
+#define _BSD_SOURCE
+#define _GNU_SOURCE 1
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <base64.h>
+#include <roken.h>
+#include <heimbase.h>
+#include <krb5.h>
+#include <common_plugin.h>
+#include <gssapi/gssapi.h>
+#include <token_validator_plugin.h>
+
+static int
+display_status(krb5_context context,
+ OM_uint32 major,
+ OM_uint32 minor,
+ gss_cred_id_t acred,
+ gss_ctx_id_t gctx,
+ gss_OID mech_type)
+{
+ gss_buffer_desc buf = GSS_C_EMPTY_BUFFER;
+ OM_uint32 dmaj, dmin;
+ OM_uint32 more = 0;
+ char *gmmsg = NULL;
+ char *gmsg = NULL;
+ char *s = NULL;
+
+ do {
+ gss_release_buffer(&dmin, &buf);
+ dmaj = gss_display_status(&dmin, major, GSS_C_GSS_CODE, GSS_C_NO_OID,
+ &more, &buf);
+ if (GSS_ERROR(dmaj) ||
+ buf.length >= INT_MAX ||
+ asprintf(&s, "%s%s%.*s", gmsg ? gmsg : "", gmsg ? ": " : "",
+ (int)buf.length, (char *)buf.value) == -1 ||
+ s == NULL) {
+ free(gmsg);
+ gmsg = NULL;
+ break;
+ }
+ gmsg = s;
+ s = NULL;
+ } while (!GSS_ERROR(dmaj) && more);
+ if (mech_type != GSS_C_NO_OID) {
+ do {
+ gss_release_buffer(&dmin, &buf);
+ dmaj = gss_display_status(&dmin, major, GSS_C_MECH_CODE, mech_type,
+ &more, &buf);
+ if (GSS_ERROR(dmaj) ||
+ asprintf(&s, "%s%s%.*s", gmmsg ? gmmsg : "", gmmsg ? ": " : "",
+ (int)buf.length, (char *)buf.value) == -1 ||
+ s == NULL) {
+ free(gmmsg);
+ gmmsg = NULL;
+ break;
+ }
+ gmmsg = s;
+ s = NULL;
+ } while (!GSS_ERROR(dmaj) && more);
+ }
+ if (gmsg == NULL)
+ krb5_set_error_message(context, ENOMEM, "Error displaying GSS-API "
+ "status");
+ else
+ krb5_set_error_message(context, EACCES, "%s%s%s%s", gmmsg,
+ gmmsg ? " (" : "", gmmsg ? gmmsg : "",
+ gmmsg ? ")" : "");
+ if (acred && gctx)
+ krb5_prepend_error_message(context, EACCES, "Failed to validate "
+ "Negotiate token due to error examining "
+ "GSS-API security context");
+ else if (acred)
+ krb5_prepend_error_message(context, EACCES, "Failed to validate "
+ "Negotiate token due to error accepting "
+ "GSS-API security context token");
+ else
+ krb5_prepend_error_message(context, EACCES, "Failed to validate "
+ "Negotiate token due to error acquiring "
+ "GSS-API default acceptor credential");
+ return EACCES;
+}
+
+static KRB5_LIB_CALL krb5_error_code
+validate(void *ctx,
+ krb5_context context,
+ const char *realm,
+ const char *token_type,
+ krb5_data *token,
+ const char * const *audiences,
+ size_t naudiences,
+ krb5_boolean *result,
+ krb5_principal *actual_principal,
+ krb5_times *token_times)
+{
+ gss_buffer_desc adisplay_name = GSS_C_EMPTY_BUFFER;
+ gss_buffer_desc idisplay_name = GSS_C_EMPTY_BUFFER;
+ gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
+ gss_buffer_desc input_token;
+ gss_cred_id_t acred = GSS_C_NO_CREDENTIAL;
+ gss_ctx_id_t gctx = GSS_C_NO_CONTEXT;
+ gss_name_t aname = GSS_C_NO_NAME;
+ gss_name_t iname = GSS_C_NO_NAME;
+ gss_OID mech_type = GSS_C_NO_OID;
+ const char *kt = krb5_config_get_string(context, NULL, "kdc",
+ "negotiate_token_validator",
+ "keytab", NULL);
+ OM_uint32 major, minor, ret_flags, time_rec;
+ size_t i;
+ char *token_decoded = NULL;
+ void *token_copy = NULL;
+ char *princ_str = NULL;
+ int ret = 0;
+
+ if (strcmp(token_type, "Negotiate") != 0)
+ return KRB5_PLUGIN_NO_HANDLE;
+
+ if (kt) {
+ gss_key_value_element_desc store_keytab_kv;
+ gss_key_value_set_desc store;
+ gss_OID_desc mech_set[2] = { *GSS_KRB5_MECHANISM, *GSS_SPNEGO_MECHANISM };
+ gss_OID_set_desc mechs = { 2, mech_set };
+
+ store_keytab_kv.key = "keytab";
+ store_keytab_kv.value = kt;
+ store.elements = &store_keytab_kv;
+ store.count = 1;
+ major = gss_acquire_cred_from(&minor, GSS_C_NO_NAME, GSS_C_INDEFINITE,
+ &mechs, GSS_C_ACCEPT, &store, &acred, NULL,
+ NULL);
+ if (major != GSS_S_COMPLETE)
+ return display_status(context, major, minor, acred, gctx, mech_type);
+
+ mechs.count = 1;
+ major = gss_set_neg_mechs(&minor, acred, &mechs);
+ if (major != GSS_S_COMPLETE)
+ return display_status(context, major, minor, acred, gctx, mech_type);
+ } /* else we'll use the default credential */
+
+ if ((token_decoded = malloc(token->length)) == NULL ||
+ (token_copy = calloc(1, token->length + 1)) == NULL)
+ goto enomem;
+
+ memcpy(token_copy, token->data, token->length);
+ if ((ret = rk_base64_decode(token_copy, token_decoded)) <= 0) {
+ krb5_set_error_message(context, EACCES, "Negotiate token malformed");
+ ret = EACCES;
+ goto out;
+ }
+
+ input_token.value = token_decoded;
+ input_token.length = ret;
+ major = gss_accept_sec_context(&minor, &gctx, acred, &input_token, NULL,
+ &iname, &mech_type, &output_token,
+ &ret_flags, &time_rec, NULL);
+
+ if (mech_type == GSS_C_NO_OID ||
+ !gss_oid_equal(mech_type, GSS_KRB5_MECHANISM)) {
+ krb5_set_error_message(context, ret = EACCES, "Negotiate token used "
+ "non-Kerberos mechanism");
+ goto out;
+ }
+
+ if (major != GSS_S_COMPLETE) {
+ ret = display_status(context, major, minor, acred, gctx, mech_type);
+ if (ret == 0)
+ ret = EINVAL;
+ goto out;
+ }
+
+ major = gss_inquire_context(&minor, gctx, NULL, &aname, NULL, NULL,
+ NULL, NULL, NULL);
+ if (major == GSS_S_COMPLETE)
+ major = gss_display_name(&minor, aname, &adisplay_name, NULL);
+ if (major == GSS_S_COMPLETE)
+ major = gss_display_name(&minor, iname, &idisplay_name, NULL);
+ if (major != GSS_S_COMPLETE) {
+ ret = display_status(context, major, minor, acred, gctx, mech_type);
+ if (ret == 0)
+ ret = EINVAL;
+ goto out;
+ }
+
+ for (i = 0; i < naudiences; i++) {
+ const char *s = adisplay_name.value;
+ size_t slen = adisplay_name.length;
+ size_t len = strlen(audiences[i]);
+
+ if (slen >= sizeof("HTTP/") - 1 &&
+ slen >= sizeof("HTTP/") - 1 + len &&
+ memcmp(s, "HTTP/", sizeof("HTTP/") - 1) == 0 &&
+ memcmp(s + sizeof("HTTP/") - 1, audiences[i], len) == 0 &&
+ s[sizeof("HTTP/") - 1 + len] == '@')
+ break;
+ }
+ if (i == naudiences) {
+ /* This handles the case where naudiences == 0 as an error */
+ krb5_set_error_message(context, EACCES, "Negotiate token used "
+ "wrong HTTP service host acceptor name");
+ goto out;
+ }
+
+ if ((princ_str = calloc(1, idisplay_name.length + 1)) == NULL)
+ goto enomem;
+ memcpy(princ_str, idisplay_name.value, idisplay_name.length);
+ if ((ret = krb5_parse_name(context, princ_str, actual_principal)))
+ goto out;
+
+ /* XXX Need name attributes to get authtime/starttime/renew_till */
+ token_times->authtime = 0;
+ token_times->starttime = time(NULL) - 300;
+ token_times->endtime = token_times->starttime + 300 + time_rec;
+ token_times->renew_till = 0;
+
+ *result = TRUE;
+ goto out;
+
+enomem:
+ ret = krb5_enomem(context);
+out:
+ gss_delete_sec_context(&minor, &gctx, NULL);
+ gss_release_buffer(&minor, &adisplay_name);
+ gss_release_buffer(&minor, &idisplay_name);
+ gss_release_buffer(&minor, &output_token);
+ gss_release_cred(&minor, &acred);
+ gss_release_name(&minor, &aname);
+ gss_release_name(&minor, &iname);
+ free(token_decoded);
+ free(token_copy);
+ free(princ_str);
+ return ret;
+}
+
+static KRB5_LIB_CALL krb5_error_code
+negotiate_init(krb5_context context, void **c)
+{
+ *c = NULL;
+ return 0;
+}
+
+static KRB5_LIB_CALL void
+negotiate_fini(void *c)
+{
+}
+
+static krb5plugin_token_validator_ftable plug_desc =
+ { 1, negotiate_init, negotiate_fini, validate };
+
+static krb5plugin_token_validator_ftable *plugs[] = { &plug_desc };
+
+static uintptr_t
+negotiate_get_instance(const char *libname)
+{
+ if (strcmp(libname, "krb5") == 0)
+ return krb5_get_instance(libname);
+
+ return 0;
+}
+
+krb5_plugin_load_ft kdc_token_validator_plugin_load;
+
+krb5_error_code KRB5_CALLCONV
+kdc_token_validator_plugin_load(heim_pcontext context,
+ krb5_get_instance_func_t *get_instance,
+ size_t *num_plugins,
+ krb5_plugin_common_ftable_cp **plugins)
+{
+ *get_instance = negotiate_get_instance;
+ *num_plugins = sizeof(plugs) / sizeof(plugs[0]);
+ *plugins = (krb5_plugin_common_ftable_cp *)plugs;
+ return 0;
+}
diff --git a/third_party/heimdal/kdc/pkinit-ec.c b/third_party/heimdal/kdc/pkinit-ec.c
new file mode 100644
index 0000000..31a5fe7
--- /dev/null
+++ b/third_party/heimdal/kdc/pkinit-ec.c
@@ -0,0 +1,548 @@
+/*
+ * Copyright (c) 2016 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <config.h>
+#include <roken.h>
+
+#ifdef PKINIT
+
+/*
+ * As with the other *-ec.c files in Heimdal, this is a bit of a hack.
+ *
+ * The idea is to use OpenSSL for EC because hcrypto doesn't have the
+ * required functionality at this time. To do this we segregate
+ * EC-using code into separate source files and then we arrange for them
+ * to get the OpenSSL headers and not the conflicting hcrypto ones.
+ *
+ * Because of auto-generated *-private.h headers, we end up needing to
+ * make sure various types are defined before we include them, thus the
+ * strange header include order here.
+ */
+
+#ifdef HAVE_HCRYPTO_W_OPENSSL
+#include <openssl/evp.h>
+#include <openssl/ec.h>
+#include <openssl/ecdsa.h>
+#include <openssl/rsa.h>
+#include <openssl/bn.h>
+#include <openssl/dh.h>
+#include <openssl/objects.h>
+#ifdef HAVE_OPENSSL_30
+#include <openssl/core_names.h>
+#endif
+#define HEIM_NO_CRYPTO_HDRS
+#endif /* HAVE_HCRYPTO_W_OPENSSL */
+
+#define NO_HCRYPTO_POLLUTION
+
+#include "kdc_locl.h"
+#include <hcrypto/des.h>
+#include <heim_asn1.h>
+#include <rfc2459_asn1.h>
+#include <cms_asn1.h>
+#include <pkinit_asn1.h>
+
+#include <hx509.h>
+#include "../lib/hx509/hx_locl.h"
+#include <hx509-private.h>
+
+void
+_kdc_pk_free_client_ec_param(krb5_context context,
+ void *k0,
+ void *k1)
+{
+#ifdef HAVE_HCRYPTO_W_OPENSSL
+#ifdef HAVE_OPENSSL_30
+ EVP_PKEY_free(k0);
+ EVP_PKEY_free(k1);
+#else
+ EC_KEY_free(k0);
+ EC_KEY_free(k1);
+#endif
+#endif
+}
+
+#ifdef HAVE_HCRYPTO_W_OPENSSL
+#ifdef HAVE_OPENSSL_30
+static krb5_error_code
+generate_ecdh_keyblock_ossl30(krb5_context context,
+ EVP_PKEY *ec_key_pub, /* the client's public key */
+ EVP_PKEY **ec_key_priv, /* the KDC's ephemeral private */
+ unsigned char **dh_gen_key, /* shared secret */
+ size_t *dh_gen_keylen)
+{
+ EVP_PKEY_CTX *pctx = NULL;
+ EVP_PKEY *ephemeral = NULL;
+ krb5_error_code ret = 0;
+ unsigned char *p = NULL;
+ size_t size = 0;
+
+ if (ec_key_pub == NULL)
+ /* XXX This seems like an internal error that should be impossible */
+ krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC,
+ "Missing client ECDH key agreement public key");
+ if (ret == 0 &&
+ (ephemeral =
+ EVP_EC_gen(OSSL_EC_curve_nid2name(NID_X9_62_prime256v1))) == NULL)
+ krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC,
+ "Could not generate an ECDH key agreement private key");
+ if (ret == 0 &&
+ (pctx = EVP_PKEY_CTX_new(ephemeral, NULL)) == NULL)
+ ret = krb5_enomem(context);
+ if (ret == 0 && EVP_PKEY_derive_init(pctx) != 1)
+ ret = krb5_enomem(context);
+ if (ret == 0 &&
+ EVP_PKEY_CTX_set_ecdh_kdf_type(pctx, EVP_PKEY_ECDH_KDF_NONE) != 1)
+ krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC,
+ "Could not generate an ECDH key agreement private key "
+ "(EVP_PKEY_CTX_set_dh_kdf_type)");
+ if (ret == 0 &&
+ EVP_PKEY_derive_set_peer_ex(pctx, ec_key_pub, 1) != 1)
+ krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC,
+ "Could not generate an ECDH key agreement private key "
+ "(EVP_PKEY_derive_set_peer_ex)");
+ if (ret == 0 &&
+ (EVP_PKEY_derive(pctx, NULL, &size) != 1 || size == 0))
+ krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC,
+ "Could not generate an ECDH key agreement private key "
+ "(EVP_PKEY_derive)");
+ if (ret == 0 && (p = malloc(size)) == NULL)
+ ret = krb5_enomem(context);
+ if (ret == 0 &&
+ (EVP_PKEY_derive(pctx, p, &size) != 1 || size == 0))
+ krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC,
+ "Could not generate an ECDH key agreement private key "
+ "(EVP_PKEY_derive)");
+
+ if (ret) {
+ EVP_PKEY_free(ephemeral);
+ ephemeral = NULL;
+ free(p);
+ p = NULL;
+ size = 0;
+ }
+
+ *ec_key_priv = ephemeral;
+ *dh_gen_keylen = size;
+ *dh_gen_key = p;
+
+ EVP_PKEY_CTX_free(pctx);
+ return ret;
+}
+#else
+
+/* The empty line above is intentional to work around an mkproto bug */
+static krb5_error_code
+generate_ecdh_keyblock_ossl11(krb5_context context,
+ EC_KEY *ec_key_pk, /* the client's public key */
+ EC_KEY **ec_key_key, /* the KDC's ephemeral private */
+ unsigned char **dh_gen_key, /* shared secret */
+ size_t *dh_gen_keylen)
+{
+ const EC_GROUP *group;
+ EC_KEY *ephemeral;
+ krb5_keyblock key;
+ krb5_error_code ret;
+ unsigned char *p;
+ size_t size;
+ int len;
+
+ *dh_gen_key = NULL;
+ *dh_gen_keylen = 0;
+ *ec_key_key = NULL;
+
+ memset(&key, 0, sizeof(key));
+
+ if (ec_key_pk == NULL) {
+ ret = KRB5KRB_ERR_GENERIC;
+ krb5_set_error_message(context, ret, "public_key");
+ return ret;
+ }
+
+ group = EC_KEY_get0_group(ec_key_pk);
+ if (group == NULL) {
+ ret = KRB5KRB_ERR_GENERIC;
+ krb5_set_error_message(context, ret, "failed to get the group of "
+ "the client's public key");
+ return ret;
+ }
+
+ ephemeral = EC_KEY_new();
+ if (ephemeral == NULL)
+ return krb5_enomem(context);
+
+ EC_KEY_set_group(ephemeral, group);
+
+ if (EC_KEY_generate_key(ephemeral) != 1) {
+ EC_KEY_free(ephemeral);
+ return krb5_enomem(context);
+ }
+
+ size = (EC_GROUP_get_degree(group) + 7) / 8;
+ p = malloc(size);
+ if (p == NULL) {
+ EC_KEY_free(ephemeral);
+ return krb5_enomem(context);
+ }
+
+ len = ECDH_compute_key(p, size,
+ EC_KEY_get0_public_key(ec_key_pk),
+ ephemeral, NULL);
+ if (len <= 0) {
+ free(p);
+ EC_KEY_free(ephemeral);
+ ret = KRB5KRB_ERR_GENERIC;
+ krb5_set_error_message(context, ret, "Failed to compute ECDH "
+ "public shared secret");
+ return ret;
+ }
+
+ *ec_key_key = ephemeral;
+ *dh_gen_key = p;
+ *dh_gen_keylen = len;
+
+ return 0;
+}
+#endif
+#endif /* HAVE_HCRYPTO_W_OPENSSL */
+
+krb5_error_code
+_kdc_generate_ecdh_keyblock(krb5_context context,
+ void *ec_key_pk, /* the client's public key */
+ void **ec_key_key, /* the KDC's ephemeral private */
+ unsigned char **dh_gen_key, /* shared secret */
+ size_t *dh_gen_keylen)
+{
+#ifdef HAVE_HCRYPTO_W_OPENSSL
+#ifdef HAVE_OPENSSL_30
+ return generate_ecdh_keyblock_ossl30(context, ec_key_pk,
+ (EVP_PKEY **)ec_key_key,
+ dh_gen_key, dh_gen_keylen);
+#else
+ return generate_ecdh_keyblock_ossl11(context, ec_key_pk,
+ (EC_KEY **)ec_key_key,
+ dh_gen_key, dh_gen_keylen);
+#endif
+#else
+ return ENOTSUP;
+#endif /* HAVE_HCRYPTO_W_OPENSSL */
+}
+
+#ifdef HAVE_HCRYPTO_W_OPENSSL
+#ifdef HAVE_OPENSSL_30
+static krb5_error_code
+get_ecdh_param_ossl30(krb5_context context,
+ krb5_kdc_configuration *config,
+ SubjectPublicKeyInfo *dh_key_info,
+ EVP_PKEY **out)
+{
+ EVP_PKEY_CTX *pctx = NULL;
+ EVP_PKEY *template = NULL;
+ EVP_PKEY *public = NULL;
+ OSSL_PARAM params[2];
+ krb5_error_code ret = 0;
+ ECParameters ecp;
+ const unsigned char *p;
+ const char *curve_sn = NULL;
+ size_t len;
+ char *curve_sn_dup = NULL;
+ int groupnid = NID_undef;
+
+ /* XXX Algorithm agility; XXX KRB5_BADMSGTYPE?? */
+
+ /*
+ * In order for d2i_PublicKey() to work we need to create a template key
+ * that has the curve parameters for the subjectPublicKey.
+ *
+ * Or maybe we could learn to use the OSSL_DECODER(3) API. But this works,
+ * at least until OpenSSL deprecates d2i_PublicKey() and forces us to use
+ * OSSL_DECODER(3).
+ */
+
+ memset(&ecp, 0, sizeof(ecp));
+
+ if (dh_key_info->algorithm.parameters == NULL)
+ krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
+ "PKINIT missing algorithm parameter "
+ "in clientPublicValue");
+ if (ret == 0)
+ ret = decode_ECParameters(dh_key_info->algorithm.parameters->data,
+ dh_key_info->algorithm.parameters->length,
+ &ecp, &len);
+ if (ret == 0 && ecp.element != choice_ECParameters_namedCurve)
+ krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
+ "PKINIT client used an unnamed curve");
+ if (ret == 0 &&
+ (groupnid = _hx509_ossl_oid2nid(&ecp.u.namedCurve)) == NID_undef)
+ krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
+ "PKINIT client used an unsupported curve");
+ if (ret == 0 && (curve_sn = OBJ_nid2sn(groupnid)) == NULL)
+ krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
+ "Could not resolve curve NID %d to its short name",
+ groupnid);
+ if (ret == 0 && (curve_sn_dup = strdup(curve_sn)) == NULL)
+ ret = krb5_enomem(context);
+ if (ret == 0) {
+ if (der_heim_oid_cmp(&ecp.u.namedCurve, &asn1_oid_id_ec_group_secp256r1) != 0)
+ krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
+ "PKINIT client used an unsupported curve");
+ }
+ if (ret == 0) {
+ /*
+ * Apparently there's no error checking to be done here? Why does
+ * OSSL_PARAM_construct_utf8_string() want a non-const for the value?
+ * Is that a bug in OpenSSL?
+ */
+ params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME,
+ curve_sn_dup, 0);
+ params[1] = OSSL_PARAM_construct_end();
+
+ if ((pctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL)) == NULL)
+ ret = krb5_enomem(context);
+ }
+ if (ret == 0 && EVP_PKEY_fromdata_init(pctx) != 1)
+ ret = krb5_enomem(context);
+ if (ret == 0 &&
+ EVP_PKEY_fromdata(pctx, &template, OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS,
+ params) != 1)
+ krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
+ "Could not set up to parse key for curve %s",
+ curve_sn);
+
+ p = dh_key_info->subjectPublicKey.data;
+ len = dh_key_info->subjectPublicKey.length / 8;
+ if (ret == 0 &&
+ (public = d2i_PublicKey(EVP_PKEY_EC, &template, &p, len)) == NULL)
+ krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
+ "Could not decode PKINIT client ECDH key");
+
+ if (ret) {
+ EVP_PKEY_free(public);
+ public = NULL;
+ }
+
+ *out = public;
+
+ /* FYI the EVP_PKEY_CTX takes ownership of the `template' key */
+ EVP_PKEY_CTX_free(pctx);
+ free_ECParameters(&ecp);
+ free(curve_sn_dup);
+ return ret;
+}
+#else
+
+static krb5_error_code
+get_ecdh_param_ossl11(krb5_context context,
+ krb5_kdc_configuration *config,
+ SubjectPublicKeyInfo *dh_key_info,
+ EC_KEY **out)
+{
+ ECParameters ecp;
+ EC_KEY *public = NULL;
+ krb5_error_code ret;
+ const unsigned char *p;
+ size_t len;
+ int nid;
+
+ if (dh_key_info->algorithm.parameters == NULL) {
+ krb5_set_error_message(context, KRB5_BADMSGTYPE,
+ "PKINIT missing algorithm parameter "
+ "in clientPublicValue");
+ return KRB5_BADMSGTYPE;
+ }
+ /* XXX Algorithm agility; XXX KRB5_BADMSGTYPE?? */
+
+ memset(&ecp, 0, sizeof(ecp));
+
+ ret = decode_ECParameters(dh_key_info->algorithm.parameters->data,
+ dh_key_info->algorithm.parameters->length, &ecp, &len);
+ if (ret)
+ goto out;
+
+ if (ecp.element != choice_ECParameters_namedCurve) {
+ ret = KRB5_BADMSGTYPE;
+ goto out;
+ }
+
+ if (der_heim_oid_cmp(&ecp.u.namedCurve, &asn1_oid_id_ec_group_secp256r1) == 0)
+ nid = NID_X9_62_prime256v1;
+ else {
+ ret = KRB5_BADMSGTYPE;
+ goto out;
+ }
+
+ /* XXX verify group is ok */
+
+ public = EC_KEY_new_by_curve_name(nid);
+
+ p = dh_key_info->subjectPublicKey.data;
+ len = dh_key_info->subjectPublicKey.length / 8;
+ if (o2i_ECPublicKey(&public, &p, len) == NULL) {
+ ret = KRB5_BADMSGTYPE;
+ krb5_set_error_message(context, ret,
+ "PKINIT failed to decode ECDH key");
+ goto out;
+ }
+ *out = public;
+ public = NULL;
+
+ out:
+ if (public)
+ EC_KEY_free(public);
+ free_ECParameters(&ecp);
+ return ret;
+}
+#endif
+#endif /* HAVE_HCRYPTO_W_OPENSSL */
+
+krb5_error_code
+_kdc_get_ecdh_param(krb5_context context,
+ krb5_kdc_configuration *config,
+ SubjectPublicKeyInfo *dh_key_info,
+ void **out)
+{
+#ifdef HAVE_HCRYPTO_W_OPENSSL
+#ifdef HAVE_OPENSSL_30
+ return get_ecdh_param_ossl30(context, config, dh_key_info, (EVP_PKEY **)out);
+#else
+ return get_ecdh_param_ossl11(context, config, dh_key_info, (EC_KEY **)out);
+#endif
+#else
+ return ENOTSUP;
+#endif /* HAVE_HCRYPTO_W_OPENSSL */
+}
+
+
+/*
+ *
+ */
+
+#ifdef HAVE_HCRYPTO_W_OPENSSL
+#ifdef HAVE_OPENSSL_30
+static krb5_error_code
+serialize_ecdh_key_ossl30(krb5_context context,
+ EVP_PKEY *key,
+ unsigned char **out,
+ size_t *out_len)
+{
+ unsigned char *p;
+ int len;
+
+ *out = NULL;
+ *out_len = 0;
+
+ len = i2d_PublicKey(key, NULL);
+ if (len <= 0) {
+ krb5_set_error_message(context, EOVERFLOW,
+ "PKINIT failed to encode ECDH key");
+ return EOVERFLOW;
+ }
+
+ *out = malloc(len);
+ if (*out == NULL)
+ return krb5_enomem(context);
+
+ p = *out;
+ len = i2d_PublicKey(key, &p);
+ if (len <= 0) {
+ free(*out);
+ *out = NULL;
+ krb5_set_error_message(context, EINVAL /* XXX Better error please */,
+ "PKINIT failed to encode ECDH key");
+ return EINVAL;
+ }
+
+ *out_len = len * 8;
+ return 0;
+}
+#else
+
+static krb5_error_code
+serialize_ecdh_key_ossl11(krb5_context context,
+ EC_KEY *key,
+ unsigned char **out,
+ size_t *out_len)
+{
+ unsigned char *p;
+ int len;
+
+ *out = NULL;
+ *out_len = 0;
+
+ len = i2o_ECPublicKey(key, NULL);
+ if (len <= 0) {
+ krb5_set_error_message(context, EOVERFLOW,
+ "PKINIT failed to encode ECDH key");
+ return EOVERFLOW;
+ }
+
+ *out = malloc(len);
+ if (*out == NULL)
+ return krb5_enomem(context);
+
+ p = *out;
+ len = i2o_ECPublicKey(key, &p);
+ if (len <= 0) {
+ free(*out);
+ *out = NULL;
+ krb5_set_error_message(context, EINVAL /* XXX Better error please */,
+ "PKINIT failed to encode ECDH key");
+ return EINVAL;
+ }
+
+ *out_len = len * 8;
+ return 0;
+}
+#endif
+#endif
+
+krb5_error_code
+_kdc_serialize_ecdh_key(krb5_context context,
+ void *key,
+ unsigned char **out,
+ size_t *out_len)
+{
+#ifdef HAVE_HCRYPTO_W_OPENSSL
+#ifdef HAVE_OPENSSL_30
+ return serialize_ecdh_key_ossl30(context, key, out, out_len);
+#else
+ return serialize_ecdh_key_ossl11(context, key, out, out_len);
+#endif
+#else
+ return ENOTSUP;
+#endif
+}
+
+#endif
diff --git a/third_party/heimdal/kdc/pkinit.c b/third_party/heimdal/kdc/pkinit.c
new file mode 100644
index 0000000..c853359
--- /dev/null
+++ b/third_party/heimdal/kdc/pkinit.c
@@ -0,0 +1,2223 @@
+/*
+ * Copyright (c) 2003 - 2016 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "kdc_locl.h"
+
+#ifdef PKINIT
+
+#include <heim_asn1.h>
+#include <rfc2459_asn1.h>
+#include <cms_asn1.h>
+#include <pkinit_asn1.h>
+
+#include <hx509.h>
+#include "crypto-headers.h"
+
+struct pk_client_params {
+ enum krb5_pk_type type;
+ enum keyex_enum keyex;
+ union {
+ struct {
+ BIGNUM *public_key;
+ DH *key;
+ } dh;
+ struct {
+ void *public_key;
+ void *key;
+ } ecdh;
+ } u;
+ hx509_cert cert;
+ krb5_timestamp endtime;
+ krb5_timestamp max_life;
+ unsigned nonce;
+ EncryptionKey reply_key;
+ char *dh_group_name;
+ hx509_peer_info peer;
+ hx509_certs client_anchors;
+ hx509_verify_ctx verify_ctx;
+ heim_octet_string *freshness_token;
+};
+
+struct pk_principal_mapping {
+ unsigned int len;
+ struct pk_allowed_princ {
+ krb5_principal principal;
+ char *subject;
+ } *val;
+};
+
+static struct krb5_pk_identity *kdc_identity;
+static struct pk_principal_mapping principal_mappings;
+static struct krb5_dh_moduli **moduli;
+
+static struct {
+ krb5_data data;
+ time_t expire;
+ time_t next_update;
+} ocsp;
+
+/*
+ *
+ */
+
+static krb5_error_code
+pk_check_pkauthenticator_win2k(krb5_context context,
+ PKAuthenticator_Win2k *a,
+ const KDC_REQ *req)
+{
+ krb5_timestamp now;
+
+ krb5_timeofday (context, &now);
+
+ /* XXX cusec */
+ if (a->ctime == 0 || labs(a->ctime - now) > context->max_skew) {
+ krb5_clear_error_message(context);
+ return KRB5KRB_AP_ERR_SKEW;
+ }
+ return 0;
+}
+
+static krb5_error_code
+pk_check_pkauthenticator(krb5_context context,
+ const PKAuthenticator *a,
+ const KDC_REQ *req)
+{
+ krb5_error_code ret;
+ krb5_timestamp now;
+ Checksum checksum;
+
+ krb5_timeofday (context, &now);
+
+ /* XXX cusec */
+ if (a->ctime == 0 || labs(a->ctime - now) > context->max_skew) {
+ krb5_clear_error_message(context);
+ return KRB5KRB_AP_ERR_SKEW;
+ }
+
+ ret = krb5_create_checksum(context,
+ NULL,
+ 0,
+ CKSUMTYPE_SHA1,
+ req->req_body._save.data,
+ req->req_body._save.length,
+ &checksum);
+ if (ret) {
+ krb5_clear_error_message(context);
+ return ret;
+ }
+
+ if (a->paChecksum == NULL) {
+ krb5_clear_error_message(context);
+ ret = KRB5_KDC_ERR_PA_CHECKSUM_MUST_BE_INCLUDED;
+ goto out;
+ }
+
+ if (der_heim_octet_string_cmp(a->paChecksum, &checksum.checksum) != 0) {
+ krb5_clear_error_message(context);
+ ret = KRB5KRB_ERR_GENERIC;
+ }
+
+out:
+ free_Checksum(&checksum);
+
+ return ret;
+}
+
+void
+_kdc_pk_free_client_param(krb5_context context, pk_client_params *cp)
+{
+ if (cp == NULL)
+ return;
+ if (cp->cert)
+ hx509_cert_free(cp->cert);
+ if (cp->verify_ctx)
+ hx509_verify_destroy_ctx(cp->verify_ctx);
+ if (cp->keyex == USE_DH) {
+ if (cp->u.dh.key)
+ DH_free(cp->u.dh.key);
+ if (cp->u.dh.public_key)
+ BN_free(cp->u.dh.public_key);
+ }
+ if (cp->keyex == USE_ECDH)
+ _kdc_pk_free_client_ec_param(context, cp->u.ecdh.key,
+ cp->u.ecdh.public_key);
+ krb5_free_keyblock_contents(context, &cp->reply_key);
+ if (cp->dh_group_name)
+ free(cp->dh_group_name);
+ if (cp->peer)
+ hx509_peer_info_free(cp->peer);
+ if (cp->client_anchors)
+ hx509_certs_free(&cp->client_anchors);
+ if (cp->freshness_token)
+ der_free_octet_string(cp->freshness_token);
+ free(cp->freshness_token);
+ memset(cp, 0, sizeof(*cp));
+ free(cp);
+}
+
+static krb5_error_code
+generate_dh_keyblock(krb5_context context,
+ pk_client_params *client_params,
+ krb5_enctype enctype)
+{
+ unsigned char *dh_gen_key = NULL;
+ krb5_keyblock key;
+ krb5_error_code ret;
+ size_t dh_gen_keylen, size;
+
+ memset(&key, 0, sizeof(key));
+
+ if (client_params->keyex == USE_DH) {
+
+ if (client_params->u.dh.public_key == NULL) {
+ ret = KRB5KRB_ERR_GENERIC;
+ krb5_set_error_message(context, ret, "missing DH public_key");
+ goto out;
+ }
+
+ if (!DH_generate_key(client_params->u.dh.key)) {
+ ret = KRB5KRB_ERR_GENERIC;
+ krb5_set_error_message(context, ret,
+ "Can't generate Diffie-Hellman keys");
+ goto out;
+ }
+
+ size = DH_size(client_params->u.dh.key);
+
+ dh_gen_key = malloc(size);
+ if (dh_gen_key == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+
+ dh_gen_keylen = DH_compute_key(dh_gen_key,client_params->u.dh.public_key, client_params->u.dh.key);
+ if (dh_gen_keylen == (size_t)-1) {
+ ret = KRB5KRB_ERR_GENERIC;
+ krb5_set_error_message(context, ret,
+ "Can't compute Diffie-Hellman key");
+ goto out;
+ }
+ if (dh_gen_keylen < size) {
+ size -= dh_gen_keylen;
+ memmove(dh_gen_key + size, dh_gen_key, dh_gen_keylen);
+ memset(dh_gen_key, 0, size);
+ dh_gen_keylen += size;
+ }
+ } else if (client_params->keyex == USE_ECDH) {
+ if (client_params->u.ecdh.public_key == NULL) {
+ ret = KRB5KRB_ERR_GENERIC;
+ krb5_set_error_message(context, ret, "missing ECDH public_key");
+ goto out;
+ }
+ ret = _kdc_generate_ecdh_keyblock(context,
+ client_params->u.ecdh.public_key,
+ &client_params->u.ecdh.key,
+ &dh_gen_key, &dh_gen_keylen);
+ if (ret)
+ goto out;
+ } else {
+ ret = KRB5KRB_ERR_GENERIC;
+ krb5_set_error_message(context, ret,
+ "Diffie-Hellman not selected keys");
+ goto out;
+ }
+
+ ret = _krb5_pk_octetstring2key(context,
+ enctype,
+ dh_gen_key, dh_gen_keylen,
+ NULL, NULL,
+ &client_params->reply_key);
+
+ out:
+ if (dh_gen_key)
+ free(dh_gen_key);
+ if (key.keyvalue.data)
+ krb5_free_keyblock_contents(context, &key);
+
+ return ret;
+}
+
+static BIGNUM *
+integer_to_BN(krb5_context context, const char *field, heim_integer *f)
+{
+ BIGNUM *bn;
+
+ bn = BN_bin2bn((const unsigned char *)f->data, f->length, NULL);
+ if (bn == NULL) {
+ krb5_set_error_message(context, KRB5_BADMSGTYPE,
+ "PKINIT: parsing BN failed %s", field);
+ return NULL;
+ }
+ BN_set_negative(bn, f->negative);
+ return bn;
+}
+
+static krb5_error_code
+get_dh_param(krb5_context context,
+ krb5_kdc_configuration *config,
+ SubjectPublicKeyInfo *dh_key_info,
+ pk_client_params *client_params)
+{
+ DomainParameters dhparam;
+ DH *dh = NULL;
+ krb5_error_code ret;
+
+ memset(&dhparam, 0, sizeof(dhparam));
+
+ if ((dh_key_info->subjectPublicKey.length % 8) != 0) {
+ ret = KRB5_BADMSGTYPE;
+ krb5_set_error_message(context, ret,
+ "PKINIT: subjectPublicKey not aligned "
+ "to 8 bit boundary");
+ goto out;
+ }
+
+ if (dh_key_info->algorithm.parameters == NULL) {
+ krb5_set_error_message(context, KRB5_BADMSGTYPE,
+ "PKINIT missing algorithm parameter "
+ "in clientPublicValue");
+ return KRB5_BADMSGTYPE;
+ }
+
+ ret = decode_DomainParameters(dh_key_info->algorithm.parameters->data,
+ dh_key_info->algorithm.parameters->length,
+ &dhparam,
+ NULL);
+ if (ret) {
+ krb5_set_error_message(context, ret, "Can't decode algorithm "
+ "parameters in clientPublicValue");
+ goto out;
+ }
+
+ ret = _krb5_dh_group_ok(context, config->pkinit_dh_min_bits,
+ &dhparam.p, &dhparam.g, dhparam.q, moduli,
+ &client_params->dh_group_name);
+ if (ret) {
+ /* XXX send back proposal of better group */
+ goto out;
+ }
+
+ dh = DH_new();
+ if (dh == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "Cannot create DH structure");
+ goto out;
+ }
+ ret = KRB5_BADMSGTYPE;
+ dh->p = integer_to_BN(context, "DH prime", &dhparam.p);
+ if (dh->p == NULL)
+ goto out;
+ dh->g = integer_to_BN(context, "DH base", &dhparam.g);
+ if (dh->g == NULL)
+ goto out;
+
+ if (dhparam.q) {
+ dh->q = integer_to_BN(context, "DH p-1 factor", dhparam.q);
+ if (dh->q == NULL)
+ goto out;
+ }
+
+ {
+ heim_integer glue;
+ size_t size;
+
+ ret = decode_DHPublicKey(dh_key_info->subjectPublicKey.data,
+ dh_key_info->subjectPublicKey.length / 8,
+ &glue,
+ &size);
+ if (ret) {
+ krb5_clear_error_message(context);
+ return ret;
+ }
+
+ client_params->u.dh.public_key = integer_to_BN(context,
+ "subjectPublicKey",
+ &glue);
+ der_free_heim_integer(&glue);
+ if (client_params->u.dh.public_key == NULL) {
+ ret = KRB5_BADMSGTYPE;
+ goto out;
+ }
+ }
+
+ client_params->u.dh.key = dh;
+ dh = NULL;
+ ret = 0;
+
+ out:
+ if (dh)
+ DH_free(dh);
+ free_DomainParameters(&dhparam);
+ return ret;
+}
+
+krb5_error_code
+_kdc_pk_rd_padata(astgs_request_t priv,
+ const PA_DATA *pa,
+ pk_client_params **ret_params)
+{
+ /* XXXrcd: we use priv vs r due to a conflict */
+ krb5_context context = priv->context;
+ krb5_kdc_configuration *config = priv->config;
+ const KDC_REQ *req = &priv->req;
+ hdb_entry *client = priv->client;
+ pk_client_params *cp;
+ krb5_error_code ret;
+ heim_oid eContentType = { 0, NULL }, contentInfoOid = { 0, NULL };
+ krb5_data eContent = { 0, NULL };
+ krb5_data signed_content = { 0, NULL };
+ const char *type = "unknown type";
+ hx509_certs trust_anchors;
+ int have_data = 0;
+ const HDB_Ext_PKINIT_cert *pc;
+
+ *ret_params = NULL;
+
+ if (!config->enable_pkinit) {
+ kdc_log(context, config, 0, "PKINIT request but PKINIT not enabled");
+ krb5_clear_error_message(context);
+ return 0;
+ }
+
+ cp = calloc(1, sizeof(*cp));
+ if (cp == NULL) {
+ krb5_clear_error_message(context);
+ ret = ENOMEM;
+ goto out;
+ }
+
+ ret = hx509_certs_init(context->hx509ctx,
+ "MEMORY:trust-anchors",
+ 0, NULL, &trust_anchors);
+ if (ret) {
+ krb5_set_error_message(context, ret, "failed to create trust anchors");
+ goto out;
+ }
+
+ ret = hx509_certs_merge(context->hx509ctx, trust_anchors,
+ kdc_identity->anchors);
+ if (ret) {
+ hx509_certs_free(&trust_anchors);
+ krb5_set_error_message(context, ret, "failed to create verify context");
+ goto out;
+ }
+
+ /* Add any registered certificates for this client as trust anchors */
+ ret = hdb_entry_get_pkinit_cert(client, &pc);
+ if (ret == 0 && pc != NULL) {
+ hx509_cert cert;
+ unsigned int i;
+
+ for (i = 0; i < pc->len; i++) {
+ cert = hx509_cert_init_data(context->hx509ctx,
+ pc->val[i].cert.data,
+ pc->val[i].cert.length,
+ NULL);
+ if (cert == NULL)
+ continue;
+ hx509_certs_add(context->hx509ctx, trust_anchors, cert);
+ hx509_cert_free(cert);
+ }
+ }
+
+ ret = hx509_verify_init_ctx(context->hx509ctx, &cp->verify_ctx);
+ if (ret) {
+ hx509_certs_free(&trust_anchors);
+ krb5_set_error_message(context, ret, "failed to create verify context");
+ goto out;
+ }
+
+ hx509_verify_set_time(cp->verify_ctx, kdc_time);
+ hx509_verify_attach_anchors(cp->verify_ctx, trust_anchors);
+ hx509_certs_free(&trust_anchors);
+
+ hx509_verify_attach_revoke(cp->verify_ctx, kdc_identity->revokectx);
+
+ if (config->pkinit_allow_proxy_certs)
+ hx509_verify_set_proxy_certificate(cp->verify_ctx, 1);
+
+ if (pa->padata_type == KRB5_PADATA_PK_AS_REQ_WIN) {
+ PA_PK_AS_REQ_Win2k r;
+
+ type = "PK-INIT-Win2k";
+
+ if (_kdc_is_anonymous(context, client->principal)) {
+ ret = KRB5_KDC_ERR_PUBLIC_KEY_ENCRYPTION_NOT_SUPPORTED;
+ krb5_set_error_message(context, ret,
+ "Anonymous client not supported in RSA mode");
+ goto out;
+ }
+
+ ret = decode_PA_PK_AS_REQ_Win2k(pa->padata_value.data,
+ pa->padata_value.length,
+ &r,
+ NULL);
+ if (ret) {
+ krb5_set_error_message(context, ret, "Can't decode "
+ "PK-AS-REQ-Win2k: %d", ret);
+ goto out;
+ }
+
+ ret = hx509_cms_unwrap_ContentInfo(&r.signed_auth_pack,
+ &contentInfoOid,
+ &signed_content,
+ &have_data);
+ free_PA_PK_AS_REQ_Win2k(&r);
+ if (ret) {
+ krb5_set_error_message(context, ret,
+ "Can't unwrap ContentInfo(win): %d", ret);
+ goto out;
+ }
+
+ } else if (pa->padata_type == KRB5_PADATA_PK_AS_REQ) {
+ PA_PK_AS_REQ r;
+
+ type = "PK-INIT-IETF";
+
+ ret = decode_PA_PK_AS_REQ(pa->padata_value.data,
+ pa->padata_value.length,
+ &r,
+ NULL);
+ if (ret) {
+ krb5_set_error_message(context, ret,
+ "Can't decode PK-AS-REQ: %d", ret);
+ goto out;
+ }
+
+ /* XXX look at r.kdcPkId */
+ if (r.trustedCertifiers) {
+ ExternalPrincipalIdentifiers *edi = r.trustedCertifiers;
+ unsigned int i, maxedi;
+
+ ret = hx509_certs_init(context->hx509ctx,
+ "MEMORY:client-anchors",
+ 0, NULL,
+ &cp->client_anchors);
+ if (ret) {
+ krb5_set_error_message(context, ret,
+ "Can't allocate client anchors: %d",
+ ret);
+ goto out;
+
+ }
+ /*
+ * If the client sent more than 10 EDIs, don't bother
+ * looking at more than 10 for performance reasons.
+ */
+ maxedi = edi->len;
+ if (maxedi > 10)
+ maxedi = 10;
+ for (i = 0; i < maxedi; i++) {
+ IssuerAndSerialNumber iasn;
+ hx509_query *q;
+ hx509_cert cert;
+ size_t size;
+
+ if (edi->val[i].issuerAndSerialNumber == NULL)
+ continue;
+
+ ret = hx509_query_alloc(context->hx509ctx, &q);
+ if (ret) {
+ krb5_set_error_message(context, ret,
+ "Failed to allocate hx509_query");
+ goto out;
+ }
+
+ ret = decode_IssuerAndSerialNumber(edi->val[i].issuerAndSerialNumber->data,
+ edi->val[i].issuerAndSerialNumber->length,
+ &iasn,
+ &size);
+ if (ret) {
+ hx509_query_free(context->hx509ctx, q);
+ continue;
+ }
+ ret = hx509_query_match_issuer_serial(q, &iasn.issuer, &iasn.serialNumber);
+ free_IssuerAndSerialNumber(&iasn);
+ if (ret) {
+ hx509_query_free(context->hx509ctx, q);
+ continue;
+ }
+
+ ret = hx509_certs_find(context->hx509ctx,
+ kdc_identity->certs,
+ q,
+ &cert);
+ hx509_query_free(context->hx509ctx, q);
+ if (ret)
+ continue;
+ hx509_certs_add(context->hx509ctx,
+ cp->client_anchors, cert);
+ hx509_cert_free(cert);
+ }
+ }
+
+ ret = hx509_cms_unwrap_ContentInfo(&r.signedAuthPack,
+ &contentInfoOid,
+ &signed_content,
+ &have_data);
+ free_PA_PK_AS_REQ(&r);
+ if (ret) {
+ krb5_set_error_message(context, ret,
+ "Can't unwrap ContentInfo: %d", ret);
+ goto out;
+ }
+
+ } else {
+ krb5_clear_error_message(context);
+ ret = KRB5KDC_ERR_PADATA_TYPE_NOSUPP;
+ goto out;
+ }
+
+ ret = der_heim_oid_cmp(&contentInfoOid, &asn1_oid_id_pkcs7_signedData);
+ if (ret != 0) {
+ ret = KRB5KRB_ERR_GENERIC;
+ krb5_set_error_message(context, ret,
+ "PK-AS-REQ-Win2k invalid content type oid");
+ goto out;
+ }
+
+ if (!have_data) {
+ ret = KRB5KRB_ERR_GENERIC;
+ krb5_set_error_message(context, ret,
+ "PK-AS-REQ-Win2k no signed auth pack");
+ goto out;
+ }
+
+ {
+ hx509_certs signer_certs;
+ int flags = HX509_CMS_VS_ALLOW_DATA_OID_MISMATCH; /* BTMM */
+
+ if (_kdc_is_anonymous(context, client->principal)
+ || (config->historical_anon_realm && _kdc_is_anon_request(req)))
+ flags |= HX509_CMS_VS_ALLOW_ZERO_SIGNER;
+
+ ret = hx509_cms_verify_signed(context->hx509ctx,
+ cp->verify_ctx,
+ flags,
+ signed_content.data,
+ signed_content.length,
+ NULL,
+ kdc_identity->certpool,
+ &eContentType,
+ &eContent,
+ &signer_certs);
+ if (ret) {
+ char *s = hx509_get_error_string(context->hx509ctx, ret);
+ krb5_warnx(context, "PKINIT: failed to verify signature: %s: %d",
+ s, ret);
+ free(s);
+ goto out;
+ }
+
+ if (signer_certs) {
+ ret = hx509_get_one_cert(context->hx509ctx, signer_certs,
+ &cp->cert);
+ hx509_certs_free(&signer_certs);
+ }
+ if (ret)
+ goto out;
+ }
+
+ /* Signature is correct, now verify the signed message */
+ if (der_heim_oid_cmp(&eContentType, &asn1_oid_id_pkcs7_data) != 0 &&
+ der_heim_oid_cmp(&eContentType, &asn1_oid_id_pkauthdata) != 0)
+ {
+ ret = KRB5_BADMSGTYPE;
+ krb5_set_error_message(context, ret, "got wrong oid for PK AuthData");
+ goto out;
+ }
+
+ if (pa->padata_type == KRB5_PADATA_PK_AS_REQ_WIN) {
+ AuthPack_Win2k ap;
+
+ ret = decode_AuthPack_Win2k(eContent.data,
+ eContent.length,
+ &ap,
+ NULL);
+ if (ret) {
+ krb5_set_error_message(context, ret,
+ "Can't decode AuthPack: %d", ret);
+ goto out;
+ }
+
+ ret = pk_check_pkauthenticator_win2k(context,
+ &ap.pkAuthenticator,
+ req);
+ if (ret) {
+ free_AuthPack_Win2k(&ap);
+ goto out;
+ }
+
+ cp->type = PKINIT_WIN2K;
+ cp->nonce = ap.pkAuthenticator.nonce;
+
+ if (ap.clientPublicValue) {
+ ret = KRB5KRB_ERR_GENERIC;
+ krb5_set_error_message(context, ret,
+ "DH not supported for Win2k");
+ free_AuthPack_Win2k(&ap);
+ goto out;
+ }
+ free_AuthPack_Win2k(&ap);
+
+ } else if (pa->padata_type == KRB5_PADATA_PK_AS_REQ) {
+ AuthPack ap;
+
+ ret = decode_AuthPack(eContent.data,
+ eContent.length,
+ &ap,
+ NULL);
+ if (ret) {
+ krb5_set_error_message(context, ret,
+ "Can't decode AuthPack: %d", ret);
+ free_AuthPack(&ap);
+ goto out;
+ }
+
+ if (_kdc_is_anonymous(context, client->principal) &&
+ ap.clientPublicValue == NULL) {
+ free_AuthPack(&ap);
+ ret = KRB5_KDC_ERR_PUBLIC_KEY_ENCRYPTION_NOT_SUPPORTED;
+ krb5_set_error_message(context, ret,
+ "Anonymous client not supported in RSA mode");
+ goto out;
+ }
+
+ ret = pk_check_pkauthenticator(context,
+ &ap.pkAuthenticator,
+ req);
+ if (ret) {
+ free_AuthPack(&ap);
+ goto out;
+ }
+
+ cp->type = PKINIT_27;
+ cp->nonce = ap.pkAuthenticator.nonce;
+
+ if (ap.clientPublicValue) {
+ if (der_heim_oid_cmp(&ap.clientPublicValue->algorithm.algorithm, &asn1_oid_id_dhpublicnumber) == 0) {
+ cp->keyex = USE_DH;
+ ret = get_dh_param(context, config,
+ ap.clientPublicValue, cp);
+ } else if (der_heim_oid_cmp(&ap.clientPublicValue->algorithm.algorithm, &asn1_oid_id_ecPublicKey) == 0) {
+ cp->keyex = USE_ECDH;
+ ret = _kdc_get_ecdh_param(context, config,
+ ap.clientPublicValue,
+ &cp->u.ecdh.public_key);
+ } else {
+ ret = KRB5_BADMSGTYPE;
+ krb5_set_error_message(context, ret,
+ "PKINIT unknown DH mechanism");
+ }
+ if (ret) {
+ free_AuthPack(&ap);
+ goto out;
+ }
+ } else
+ cp->keyex = USE_RSA;
+
+ ret = hx509_peer_info_alloc(context->hx509ctx,
+ &cp->peer);
+ if (ret) {
+ free_AuthPack(&ap);
+ goto out;
+ }
+
+ if (ap.supportedCMSTypes) {
+ ret = hx509_peer_info_set_cms_algs(context->hx509ctx,
+ cp->peer,
+ ap.supportedCMSTypes->val,
+ ap.supportedCMSTypes->len);
+ if (ret) {
+ free_AuthPack(&ap);
+ goto out;
+ }
+ } else {
+ /* assume old client */
+ hx509_peer_info_add_cms_alg(context->hx509ctx, cp->peer,
+ hx509_crypto_des_rsdi_ede3_cbc());
+ hx509_peer_info_add_cms_alg(context->hx509ctx, cp->peer,
+ hx509_signature_rsa_with_sha1());
+ hx509_peer_info_add_cms_alg(context->hx509ctx, cp->peer,
+ hx509_signature_sha1());
+ }
+
+ /*
+ * Copy the freshness token into the out parameters if it is present.
+ */
+ if (ap.pkAuthenticator.freshnessToken != NULL) {
+ cp->freshness_token = calloc(1, sizeof (*cp->freshness_token));
+ if (cp->freshness_token == NULL) {
+ ret = ENOMEM;
+ free_AuthPack(&ap);
+ goto out;
+ }
+
+ ret = der_copy_octet_string(ap.pkAuthenticator.freshnessToken, cp->freshness_token);
+ if (ret) {
+ free_AuthPack(&ap);
+ goto out;
+ }
+ }
+
+ free_AuthPack(&ap);
+ } else
+ krb5_abortx(context, "internal pkinit error");
+
+ kdc_log(context, config, 0, "PKINIT request of type %s", type);
+
+out:
+ if (ret)
+ krb5_warn(context, ret, "PKINIT");
+
+ if (signed_content.data)
+ free(signed_content.data);
+ krb5_data_free(&eContent);
+ der_free_oid(&eContentType);
+ der_free_oid(&contentInfoOid);
+ if (ret) {
+ _kdc_pk_free_client_param(context, cp);
+ } else
+ *ret_params = cp;
+ return ret;
+}
+
+krb5_timestamp
+_kdc_pk_endtime(pk_client_params *pkp)
+{
+ return pkp->endtime;
+}
+
+krb5_timestamp
+_kdc_pk_max_life(pk_client_params *pkp)
+{
+ return pkp->max_life;
+}
+
+unsigned
+_kdc_pk_nonce(pk_client_params *pkp)
+{
+ return pkp->nonce;
+}
+
+/*
+ *
+ */
+
+static krb5_error_code
+BN_to_integer(krb5_context context, BIGNUM *bn, heim_integer *integer)
+{
+ integer->length = BN_num_bytes(bn);
+ integer->data = malloc(integer->length);
+ if (integer->data == NULL) {
+ krb5_clear_error_message(context);
+ return ENOMEM;
+ }
+ BN_bn2bin(bn, integer->data);
+ integer->negative = BN_is_negative(bn);
+ return 0;
+}
+
+static krb5_error_code
+pk_mk_pa_reply_enckey(krb5_context context,
+ krb5_kdc_configuration *config,
+ pk_client_params *cp,
+ const KDC_REQ *req,
+ const krb5_data *req_buffer,
+ krb5_keyblock *reply_key,
+ ContentInfo *content_info,
+ hx509_cert *kdc_cert)
+{
+ const heim_oid *envelopedAlg = NULL, *sdAlg = NULL, *evAlg = NULL;
+ krb5_error_code ret;
+ krb5_data buf, signed_data;
+ size_t size = 0;
+ int do_win2k = 0;
+
+ krb5_data_zero(&buf);
+ krb5_data_zero(&signed_data);
+
+ *kdc_cert = NULL;
+
+ /*
+ * If the message client is a win2k-type but it sends pa data
+ * 09-binding it expects a IETF (checksum) reply so there can be
+ * no replay attacks.
+ */
+
+ switch (cp->type) {
+ case PKINIT_WIN2K: {
+ int i = 0;
+ if (_kdc_find_padata(req, &i, KRB5_PADATA_PK_AS_09_BINDING) == NULL
+ && config->pkinit_require_binding == 0)
+ {
+ do_win2k = 1;
+ }
+ sdAlg = &asn1_oid_id_pkcs7_data;
+ evAlg = &asn1_oid_id_pkcs7_data;
+ envelopedAlg = &asn1_oid_id_rsadsi_des_ede3_cbc;
+ break;
+ }
+ case PKINIT_27:
+ sdAlg = &asn1_oid_id_pkrkeydata;
+ evAlg = &asn1_oid_id_pkcs7_signedData;
+ break;
+ default:
+ krb5_abortx(context, "internal pkinit error");
+ }
+
+ if (do_win2k) {
+ ReplyKeyPack_Win2k kp;
+ memset(&kp, 0, sizeof(kp));
+
+ ret = copy_EncryptionKey(reply_key, &kp.replyKey);
+ if (ret) {
+ krb5_clear_error_message(context);
+ goto out;
+ }
+ kp.nonce = cp->nonce;
+
+ ASN1_MALLOC_ENCODE(ReplyKeyPack_Win2k,
+ buf.data, buf.length,
+ &kp, &size,ret);
+ free_ReplyKeyPack_Win2k(&kp);
+ } else {
+ krb5_crypto ascrypto;
+ ReplyKeyPack kp;
+ memset(&kp, 0, sizeof(kp));
+
+ ret = copy_EncryptionKey(reply_key, &kp.replyKey);
+ if (ret) {
+ krb5_clear_error_message(context);
+ goto out;
+ }
+
+ ret = krb5_crypto_init(context, reply_key, 0, &ascrypto);
+ if (ret) {
+ krb5_clear_error_message(context);
+ goto out;
+ }
+
+ ret = krb5_create_checksum(context, ascrypto, 6, 0,
+ req_buffer->data, req_buffer->length,
+ &kp.asChecksum);
+ if (ret) {
+ krb5_clear_error_message(context);
+ goto out;
+ }
+
+ ret = krb5_crypto_destroy(context, ascrypto);
+ if (ret) {
+ krb5_clear_error_message(context);
+ goto out;
+ }
+ ASN1_MALLOC_ENCODE(ReplyKeyPack, buf.data, buf.length, &kp, &size,ret);
+ free_ReplyKeyPack(&kp);
+ }
+ if (ret) {
+ krb5_set_error_message(context, ret, "ASN.1 encoding of ReplyKeyPack "
+ "failed (%d)", ret);
+ goto out;
+ }
+ if (buf.length != size)
+ krb5_abortx(context, "Internal ASN.1 encoder error");
+
+ {
+ hx509_query *q;
+ hx509_cert cert;
+
+ ret = hx509_query_alloc(context->hx509ctx, &q);
+ if (ret)
+ goto out;
+
+ hx509_query_match_option(q, HX509_QUERY_OPTION_PRIVATE_KEY);
+ if (config->pkinit_kdc_friendly_name)
+ hx509_query_match_friendly_name(q, config->pkinit_kdc_friendly_name);
+
+ ret = hx509_certs_find(context->hx509ctx,
+ kdc_identity->certs,
+ q,
+ &cert);
+ hx509_query_free(context->hx509ctx, q);
+ if (ret)
+ goto out;
+
+ ret = hx509_cms_create_signed_1(context->hx509ctx,
+ 0,
+ sdAlg,
+ buf.data,
+ buf.length,
+ NULL,
+ cert,
+ cp->peer,
+ cp->client_anchors,
+ kdc_identity->certpool,
+ &signed_data);
+ *kdc_cert = cert;
+ }
+
+ krb5_data_free(&buf);
+ if (ret)
+ goto out;
+
+ if (cp->type == PKINIT_WIN2K) {
+ ret = hx509_cms_wrap_ContentInfo(&asn1_oid_id_pkcs7_signedData,
+ &signed_data,
+ &buf);
+ if (ret)
+ goto out;
+ krb5_data_free(&signed_data);
+ signed_data = buf;
+ }
+
+ ret = hx509_cms_envelope_1(context->hx509ctx,
+ HX509_CMS_EV_NO_KU_CHECK,
+ cp->cert,
+ signed_data.data, signed_data.length,
+ envelopedAlg,
+ evAlg, &buf);
+ if (ret)
+ goto out;
+
+ ret = _krb5_pk_mk_ContentInfo(context,
+ &buf,
+ &asn1_oid_id_pkcs7_envelopedData,
+ content_info);
+out:
+ if (ret && *kdc_cert) {
+ hx509_cert_free(*kdc_cert);
+ *kdc_cert = NULL;
+ }
+
+ krb5_data_free(&buf);
+ krb5_data_free(&signed_data);
+ return ret;
+}
+
+/*
+ *
+ */
+
+static krb5_error_code
+pk_mk_pa_reply_dh(krb5_context context,
+ krb5_kdc_configuration *config,
+ pk_client_params *cp,
+ ContentInfo *content_info,
+ hx509_cert *kdc_cert)
+{
+ KDCDHKeyInfo dh_info;
+ krb5_data signed_data, buf;
+ ContentInfo contentinfo;
+ krb5_error_code ret;
+ hx509_cert cert;
+ hx509_query *q;
+ size_t size = 0;
+
+ memset(&contentinfo, 0, sizeof(contentinfo));
+ memset(&dh_info, 0, sizeof(dh_info));
+ krb5_data_zero(&signed_data);
+ krb5_data_zero(&buf);
+
+ *kdc_cert = NULL;
+
+ if (cp->keyex == USE_DH) {
+ DH *kdc_dh = cp->u.dh.key;
+ heim_integer i;
+
+ ret = BN_to_integer(context, kdc_dh->pub_key, &i);
+ if (ret)
+ return ret;
+
+ ASN1_MALLOC_ENCODE(DHPublicKey, buf.data, buf.length, &i, &size, ret);
+ der_free_heim_integer(&i);
+ if (ret) {
+ krb5_set_error_message(context, ret, "ASN.1 encoding of "
+ "DHPublicKey failed (%d)", ret);
+ return ret;
+ }
+ if (buf.length != size)
+ krb5_abortx(context, "Internal ASN.1 encoder error");
+
+ dh_info.subjectPublicKey.length = buf.length * 8;
+ dh_info.subjectPublicKey.data = buf.data;
+ krb5_data_zero(&buf);
+ } else if (cp->keyex == USE_ECDH) {
+ unsigned char *p;
+ ret = _kdc_serialize_ecdh_key(context, cp->u.ecdh.key, &p,
+ &dh_info.subjectPublicKey.length);
+ if (ret)
+ goto out;
+ dh_info.subjectPublicKey.data = p;
+ } else
+ krb5_abortx(context, "no keyex selected ?");
+
+
+ dh_info.nonce = cp->nonce;
+
+ ASN1_MALLOC_ENCODE(KDCDHKeyInfo, buf.data, buf.length, &dh_info, &size,
+ ret);
+ if (ret) {
+ krb5_set_error_message(context, ret, "ASN.1 encoding of "
+ "KdcDHKeyInfo failed (%d)", ret);
+ goto out;
+ }
+ if (buf.length != size)
+ krb5_abortx(context, "Internal ASN.1 encoder error");
+
+ /*
+ * Create the SignedData structure and sign the KdcDHKeyInfo
+ * filled in above
+ */
+
+ ret = hx509_query_alloc(context->hx509ctx, &q);
+ if (ret)
+ goto out;
+
+ hx509_query_match_option(q, HX509_QUERY_OPTION_PRIVATE_KEY);
+ if (config->pkinit_kdc_friendly_name)
+ hx509_query_match_friendly_name(q, config->pkinit_kdc_friendly_name);
+
+ ret = hx509_certs_find(context->hx509ctx,
+ kdc_identity->certs,
+ q,
+ &cert);
+ hx509_query_free(context->hx509ctx, q);
+ if (ret)
+ goto out;
+
+ ret = hx509_cms_create_signed_1(context->hx509ctx,
+ 0,
+ &asn1_oid_id_pkdhkeydata,
+ buf.data,
+ buf.length,
+ NULL,
+ cert,
+ cp->peer,
+ cp->client_anchors,
+ kdc_identity->certpool,
+ &signed_data);
+ if (ret) {
+ kdc_log(context, config, 0, "Failed signing the DH* reply: %d", ret);
+ goto out;
+ }
+ *kdc_cert = cert;
+
+ ret = _krb5_pk_mk_ContentInfo(context,
+ &signed_data,
+ &asn1_oid_id_pkcs7_signedData,
+ content_info);
+ if (ret)
+ goto out;
+
+ out:
+ if (ret && *kdc_cert) {
+ hx509_cert_free(*kdc_cert);
+ *kdc_cert = NULL;
+ }
+
+ krb5_data_free(&buf);
+ krb5_data_free(&signed_data);
+ free_KDCDHKeyInfo(&dh_info);
+
+ return ret;
+}
+
+/*
+ *
+ */
+
+krb5_error_code
+_kdc_pk_mk_pa_reply(astgs_request_t r, pk_client_params *cp)
+{
+ krb5_kdc_configuration *config = r->config;
+ krb5_enctype sessionetype = r->sessionetype;
+ const KDC_REQ *req = &r->req;
+ const krb5_data *req_buffer = &r->request;
+ krb5_keyblock *reply_key = &r->reply_key;
+ krb5_keyblock *sessionkey = &r->session_key;
+ METHOD_DATA *md = r->rep.padata;
+ krb5_error_code ret;
+ void *buf = NULL;
+ size_t len = 0, size = 0;
+ krb5_enctype enctype;
+ int pa_type;
+ hx509_cert kdc_cert = NULL;
+ size_t i;
+
+ if (!config->enable_pkinit) {
+ krb5_clear_error_message(r->context);
+ return 0;
+ }
+
+ if (req->req_body.etype.len > 0) {
+ for (i = 0; i < req->req_body.etype.len; i++)
+ if (krb5_enctype_valid(r->context, req->req_body.etype.val[i]) == 0)
+ break;
+ if (req->req_body.etype.len <= i) {
+ ret = KRB5KRB_ERR_GENERIC;
+ krb5_set_error_message(r->context, ret,
+ "No valid enctype available from client");
+ goto out;
+ }
+ enctype = req->req_body.etype.val[i];
+ } else
+ enctype = ETYPE_DES3_CBC_SHA1;
+
+ if (cp->type == PKINIT_27) {
+ PA_PK_AS_REP rep;
+ const char *type, *other = "";
+
+ memset(&rep, 0, sizeof(rep));
+
+ pa_type = KRB5_PADATA_PK_AS_REP;
+
+ if (cp->keyex == USE_RSA) {
+ ContentInfo info;
+
+ type = "enckey";
+
+ rep.element = choice_PA_PK_AS_REP_encKeyPack;
+
+ ret = krb5_generate_random_keyblock(r->context, enctype,
+ &cp->reply_key);
+ if (ret) {
+ free_PA_PK_AS_REP(&rep);
+ goto out;
+ }
+ ret = pk_mk_pa_reply_enckey(r->context,
+ config,
+ cp,
+ req,
+ req_buffer,
+ &cp->reply_key,
+ &info,
+ &kdc_cert);
+ if (ret) {
+ free_PA_PK_AS_REP(&rep);
+ goto out;
+ }
+ ASN1_MALLOC_ENCODE(ContentInfo, rep.u.encKeyPack.data,
+ rep.u.encKeyPack.length, &info, &size,
+ ret);
+ free_ContentInfo(&info);
+ if (ret) {
+ krb5_set_error_message(r->context, ret, "encoding of Key ContentInfo "
+ "failed %d", ret);
+ free_PA_PK_AS_REP(&rep);
+ goto out;
+ }
+ if (rep.u.encKeyPack.length != size)
+ krb5_abortx(r->context, "Internal ASN.1 encoder error");
+
+ ret = krb5_generate_random_keyblock(r->context, sessionetype,
+ sessionkey);
+ if (ret) {
+ free_PA_PK_AS_REP(&rep);
+ goto out;
+ }
+
+ } else {
+ ContentInfo info;
+
+ switch (cp->keyex) {
+ case USE_DH: type = "dh"; break;
+ case USE_ECDH: type = "ecdh"; break;
+ default: krb5_abortx(r->context, "unknown keyex"); break;
+ }
+
+ if (cp->dh_group_name)
+ other = cp->dh_group_name;
+
+ rep.element = choice_PA_PK_AS_REP_dhInfo;
+
+ ret = generate_dh_keyblock(r->context, cp, enctype);
+ if (ret)
+ return ret;
+
+ ret = pk_mk_pa_reply_dh(r->context, config,
+ cp,
+ &info,
+ &kdc_cert);
+ if (ret) {
+ free_PA_PK_AS_REP(&rep);
+ krb5_set_error_message(r->context, ret,
+ "create pa-reply-dh "
+ "failed %d", ret);
+ goto out;
+ }
+
+ ASN1_MALLOC_ENCODE(ContentInfo, rep.u.dhInfo.dhSignedData.data,
+ rep.u.dhInfo.dhSignedData.length, &info, &size,
+ ret);
+ free_ContentInfo(&info);
+ if (ret) {
+ krb5_set_error_message(r->context, ret,
+ "encoding of Key ContentInfo "
+ "failed %d", ret);
+ free_PA_PK_AS_REP(&rep);
+ goto out;
+ }
+ if (rep.u.encKeyPack.length != size)
+ krb5_abortx(r->context, "Internal ASN.1 encoder error");
+
+ /* generate the session key using the method from RFC6112 */
+ {
+ krb5_keyblock kdc_contribution_key;
+ krb5_crypto reply_crypto;
+ krb5_crypto kdccont_crypto;
+ krb5_data p1 = { strlen("PKINIT"), "PKINIT"};
+ krb5_data p2 = { strlen("KEYEXCHANGE"), "KEYEXCHANGE"};
+ void *kckdata;
+ size_t kcklen;
+ EncryptedData kx;
+ void *kxdata;
+ size_t kxlen;
+
+ ret = krb5_generate_random_keyblock(r->context, sessionetype,
+ &kdc_contribution_key);
+ if (ret) {
+ free_PA_PK_AS_REP(&rep);
+ goto out;
+ }
+ ret = krb5_crypto_init(r->context, &cp->reply_key, enctype, &reply_crypto);
+ if (ret) {
+ krb5_free_keyblock_contents(r->context, &kdc_contribution_key);
+ free_PA_PK_AS_REP(&rep);
+ goto out;
+ }
+ ret = krb5_crypto_init(r->context, &kdc_contribution_key, sessionetype, &kdccont_crypto);
+ if (ret) {
+ krb5_crypto_destroy(r->context, reply_crypto);
+ krb5_free_keyblock_contents(r->context, &kdc_contribution_key);
+ free_PA_PK_AS_REP(&rep);
+ goto out;
+ }
+ /* KRB-FX-CF2 */
+ ret = krb5_crypto_fx_cf2(r->context, kdccont_crypto, reply_crypto,
+ &p1, &p2, sessionetype, sessionkey);
+ krb5_crypto_destroy(r->context, kdccont_crypto);
+ if (ret) {
+ krb5_crypto_destroy(r->context, reply_crypto);
+ krb5_free_keyblock_contents(r->context, &kdc_contribution_key);
+ free_PA_PK_AS_REP(&rep);
+ goto out;
+ }
+ ASN1_MALLOC_ENCODE(EncryptionKey, kckdata, kcklen,
+ &kdc_contribution_key, &size, ret);
+ krb5_free_keyblock_contents(r->context, &kdc_contribution_key);
+ if (ret) {
+ krb5_set_error_message(r->context, ret, "encoding of PKINIT-KX Key failed %d", ret);
+ krb5_crypto_destroy(r->context, reply_crypto);
+ free_PA_PK_AS_REP(&rep);
+ goto out;
+ }
+ if (kcklen != size)
+ krb5_abortx(r->context, "Internal ASN.1 encoder error");
+ ret = krb5_encrypt_EncryptedData(r->context, reply_crypto, KRB5_KU_PA_PKINIT_KX,
+ kckdata, kcklen, 0, &kx);
+ krb5_crypto_destroy(r->context, reply_crypto);
+ free(kckdata);
+ if (ret) {
+ free_PA_PK_AS_REP(&rep);
+ goto out;
+ }
+ ASN1_MALLOC_ENCODE(EncryptedData, kxdata, kxlen,
+ &kx, &size, ret);
+ free_EncryptedData(&kx);
+ if (ret) {
+ krb5_set_error_message(r->context, ret,
+ "encoding of PKINIT-KX failed %d", ret);
+ free_PA_PK_AS_REP(&rep);
+ goto out;
+ }
+ if (kxlen != size)
+ krb5_abortx(r->context, "Internal ASN.1 encoder error");
+ /* Add PA-PKINIT-KX */
+ ret = krb5_padata_add(r->context, md, KRB5_PADATA_PKINIT_KX, kxdata, kxlen);
+ if (ret) {
+ krb5_set_error_message(r->context, ret,
+ "Failed adding PKINIT-KX %d", ret);
+ free(buf);
+ goto out;
+ }
+ }
+ }
+
+#define use_btmm_with_enckey 0
+ if (use_btmm_with_enckey && rep.element == choice_PA_PK_AS_REP_encKeyPack) {
+ PA_PK_AS_REP_BTMM btmm;
+ heim_any any;
+
+ any.data = rep.u.encKeyPack.data;
+ any.length = rep.u.encKeyPack.length;
+
+ btmm.dhSignedData = NULL;
+ btmm.encKeyPack = &any;
+
+ ASN1_MALLOC_ENCODE(PA_PK_AS_REP_BTMM, buf, len, &btmm, &size, ret);
+ } else {
+ ASN1_MALLOC_ENCODE(PA_PK_AS_REP, buf, len, &rep, &size, ret);
+ }
+
+ free_PA_PK_AS_REP(&rep);
+ if (ret) {
+ krb5_set_error_message(r->context, ret,
+ "encode PA-PK-AS-REP failed %d", ret);
+ goto out;
+ }
+ if (len != size)
+ krb5_abortx(r->context, "Internal ASN.1 encoder error");
+
+ kdc_log(r->context, config, 0, "PKINIT using %s %s", type, other);
+
+ } else if (cp->type == PKINIT_WIN2K) {
+ PA_PK_AS_REP_Win2k rep;
+ ContentInfo info;
+
+ if (cp->keyex != USE_RSA) {
+ ret = KRB5KRB_ERR_GENERIC;
+ krb5_set_error_message(r->context, ret,
+ "Win2k PKINIT doesn't support DH");
+ goto out;
+ }
+
+ memset(&rep, 0, sizeof(rep));
+
+ pa_type = KRB5_PADATA_PK_AS_REP_19;
+ rep.element = choice_PA_PK_AS_REP_Win2k_encKeyPack;
+
+ ret = krb5_generate_random_keyblock(r->context, enctype,
+ &cp->reply_key);
+ if (ret) {
+ free_PA_PK_AS_REP_Win2k(&rep);
+ goto out;
+ }
+ ret = pk_mk_pa_reply_enckey(r->context,
+ config,
+ cp,
+ req,
+ req_buffer,
+ &cp->reply_key,
+ &info,
+ &kdc_cert);
+ if (ret) {
+ free_PA_PK_AS_REP_Win2k(&rep);
+ goto out;
+ }
+ ASN1_MALLOC_ENCODE(ContentInfo, rep.u.encKeyPack.data,
+ rep.u.encKeyPack.length, &info, &size,
+ ret);
+ free_ContentInfo(&info);
+ if (ret) {
+ krb5_set_error_message(r->context, ret, "encoding of Key ContentInfo "
+ "failed %d", ret);
+ free_PA_PK_AS_REP_Win2k(&rep);
+ goto out;
+ }
+ if (rep.u.encKeyPack.length != size)
+ krb5_abortx(r->context, "Internal ASN.1 encoder error");
+
+ ASN1_MALLOC_ENCODE(PA_PK_AS_REP_Win2k, buf, len, &rep, &size, ret);
+ free_PA_PK_AS_REP_Win2k(&rep);
+ if (ret) {
+ krb5_set_error_message(r->context, ret,
+ "encode PA-PK-AS-REP-Win2k failed %d", ret);
+ goto out;
+ }
+ if (len != size)
+ krb5_abortx(r->context, "Internal ASN.1 encoder error");
+
+ ret = krb5_generate_random_keyblock(r->context, sessionetype,
+ sessionkey);
+ if (ret) {
+ free(buf);
+ goto out;
+ }
+
+ } else
+ krb5_abortx(r->context, "PKINIT internal error");
+
+
+ ret = krb5_padata_add(r->context, md, pa_type, buf, len);
+ if (ret) {
+ krb5_set_error_message(r->context, ret,
+ "Failed adding PA-PK-AS-REP %d", ret);
+ free(buf);
+ goto out;
+ }
+
+ if (config->pkinit_kdc_ocsp_file) {
+
+ if (ocsp.expire == 0 && ocsp.next_update > kdc_time) {
+ struct stat sb;
+ int fd;
+
+ krb5_data_free(&ocsp.data);
+
+ ocsp.expire = 0;
+ ocsp.next_update = kdc_time + 60 * 5;
+
+ fd = open(config->pkinit_kdc_ocsp_file, O_RDONLY);
+ if (fd < 0) {
+ kdc_log(r->context, config, 0,
+ "PKINIT failed to open ocsp data file %d", errno);
+ goto out_ocsp;
+ }
+ ret = fstat(fd, &sb);
+ if (ret) {
+ ret = errno;
+ close(fd);
+ kdc_log(r->context, config, 0,
+ "PKINIT failed to stat ocsp data %d", ret);
+ goto out_ocsp;
+ }
+
+ ret = krb5_data_alloc(&ocsp.data, sb.st_size);
+ if (ret) {
+ close(fd);
+ kdc_log(r->context, config, 0,
+ "PKINIT failed to allocate ocsp data %d", ret);
+ goto out_ocsp;
+ }
+ ocsp.data.length = sb.st_size;
+ ret = read(fd, ocsp.data.data, sb.st_size);
+ close(fd);
+ if (ret != sb.st_size) {
+ kdc_log(r->context, config, 0,
+ "PKINIT failed to read ocsp data %d", errno);
+ goto out_ocsp;
+ }
+
+ ret = hx509_ocsp_verify(r->context->hx509ctx,
+ kdc_time,
+ kdc_cert,
+ 0,
+ ocsp.data.data, ocsp.data.length,
+ &ocsp.expire);
+ if (ret) {
+ kdc_log(r->context, config, 0,
+ "PKINIT failed to verify ocsp data %d", ret);
+ krb5_data_free(&ocsp.data);
+ ocsp.expire = 0;
+ } else if (ocsp.expire > 180) {
+ ocsp.expire -= 180; /* refetch the ocsp before it expires */
+ ocsp.next_update = ocsp.expire;
+ } else {
+ ocsp.next_update = kdc_time;
+ }
+ out_ocsp:
+ ret = 0;
+ }
+
+ if (ocsp.expire != 0 && ocsp.expire > kdc_time) {
+
+ ret = krb5_padata_add(r->context, md,
+ KRB5_PADATA_PA_PK_OCSP_RESPONSE,
+ ocsp.data.data, ocsp.data.length);
+ if (ret) {
+ krb5_set_error_message(r->context, ret,
+ "Failed adding OCSP response %d", ret);
+ goto out;
+ }
+ }
+ }
+
+out:
+ if (kdc_cert)
+ hx509_cert_free(kdc_cert);
+
+ if (ret == 0)
+ ret = krb5_copy_keyblock_contents(r->context, &cp->reply_key, reply_key);
+ return ret;
+}
+
+static int
+match_rfc_san(krb5_context context,
+ krb5_kdc_configuration *config,
+ hx509_context hx509ctx,
+ hx509_cert client_cert,
+ krb5_const_principal match)
+{
+ hx509_octet_string_list list;
+ int ret, found = 0;
+ size_t i;
+
+ memset(&list, 0 , sizeof(list));
+
+ ret = hx509_cert_find_subjectAltName_otherName(hx509ctx,
+ client_cert,
+ &asn1_oid_id_pkinit_san,
+ &list);
+ if (ret)
+ goto out;
+
+ for (i = 0; !found && i < list.len; i++) {
+ krb5_principal_data principal;
+ KRB5PrincipalName kn;
+ size_t size;
+
+ ret = decode_KRB5PrincipalName(list.val[i].data,
+ list.val[i].length,
+ &kn, &size);
+ if (ret) {
+ const char *msg = krb5_get_error_message(context, ret);
+ kdc_log(context, config, 0,
+ "Decoding Kerberos principal name in certificate failed: %s", msg);
+ krb5_free_error_message(context, msg);
+ break;
+ }
+ if (size != list.val[i].length) {
+ kdc_log(context, config, 0,
+ "Decoded Kerberos principal name did not have expected length");
+ return KRB5_KDC_ERR_CLIENT_NAME_MISMATCH;
+ }
+
+ memset(&principal, 0, sizeof (principal));
+ principal.name = kn.principalName;
+ principal.realm = kn.realm;
+
+ if (krb5_principal_compare(context, &principal, match) == TRUE)
+ found = 1;
+ free_KRB5PrincipalName(&kn);
+ }
+
+out:
+ hx509_free_octet_string_list(&list);
+ if (ret)
+ return ret;
+
+ if (!found)
+ return KRB5_KDC_ERR_CLIENT_NAME_MISMATCH;
+
+ return 0;
+}
+
+static int
+match_ms_upn_san(krb5_context context,
+ krb5_kdc_configuration *config,
+ hx509_context hx509ctx,
+ hx509_cert client_cert,
+ HDB *clientdb,
+ hdb_entry *client)
+{
+ hx509_octet_string_list list;
+ krb5_principal principal = NULL;
+ int ret;
+ MS_UPN_SAN upn;
+ size_t size;
+
+ memset(&list, 0 , sizeof(list));
+
+ ret = hx509_cert_find_subjectAltName_otherName(hx509ctx,
+ client_cert,
+ &asn1_oid_id_pkinit_ms_san,
+ &list);
+ if (ret)
+ goto out;
+
+ if (list.len != 1) {
+ if (list.len)
+ kdc_log(context, config, 0,
+ "More than one PKINIT MS UPN SAN");
+ else
+ kdc_log(context, config, 0,
+ "No PKINIT MS UPN SAN");
+ ret = KRB5_KDC_ERR_CLIENT_NAME_MISMATCH;
+ goto out;
+ }
+
+ ret = decode_MS_UPN_SAN(list.val[0].data, list.val[0].length, &upn, &size);
+ if (ret) {
+ kdc_log(context, config, 0, "Decode of MS-UPN-SAN failed");
+ goto out;
+ }
+ if (size != list.val[0].length) {
+ free_MS_UPN_SAN(&upn);
+ kdc_log(context, config, 0, "Trailing data in MS UPN SAN");
+ ret = KRB5_KDC_ERR_CLIENT_NAME_MISMATCH;
+ goto out;
+ }
+
+ kdc_log(context, config, 0, "found MS UPN SAN: %s", upn);
+
+ ret = krb5_parse_name(context, upn, &principal);
+ free_MS_UPN_SAN(&upn);
+ if (ret) {
+ kdc_log(context, config, 0, "Failed to parse principal in MS UPN SAN");
+ goto out;
+ }
+
+ if (clientdb->hdb_check_pkinit_ms_upn_match) {
+ ret = clientdb->hdb_check_pkinit_ms_upn_match(context, clientdb, client, principal);
+ } else {
+
+ /*
+ * This is very wrong, but will do for a fallback
+ */
+ strupr(principal->realm);
+
+ if (krb5_principal_compare(context, principal, client->principal) == FALSE)
+ ret = KRB5_KDC_ERR_CLIENT_NAME_MISMATCH;
+ }
+
+out:
+ if (principal)
+ krb5_free_principal(context, principal);
+ hx509_free_octet_string_list(&list);
+
+ return ret;
+}
+
+krb5_error_code
+_kdc_pk_check_client(astgs_request_t r,
+ pk_client_params *cp,
+ char **subject_name)
+{
+ krb5_kdc_configuration *config = r->config;
+ HDB *clientdb = r->clientdb;
+ hdb_entry *client = r->client;
+ const HDB_Ext_PKINIT_acl *acl;
+ const HDB_Ext_PKINIT_cert *pc;
+ krb5_error_code ret;
+ hx509_name name;
+ size_t i;
+
+ if (cp->cert == NULL) {
+ if (!_kdc_is_anonymous(r->context, client->principal)
+ && !config->historical_anon_realm)
+ return KRB5KDC_ERR_BADOPTION;
+
+ *subject_name = strdup("<unauthenticated anonymous client>");
+ if (*subject_name == NULL)
+ return ENOMEM;
+ return 0;
+ }
+
+ cp->endtime = hx509_cert_get_notAfter(cp->cert);
+ cp->max_life = 0;
+ if (config->pkinit_max_life_from_cert_extension)
+ cp->max_life =
+ hx509_cert_get_pkinit_max_life(r->context->hx509ctx, cp->cert,
+ config->pkinit_max_life_bound);
+ if (cp->max_life == 0 && config->pkinit_max_life_from_cert > 0) {
+ cp->max_life = cp->endtime - hx509_cert_get_notBefore(cp->cert);
+ if (cp->max_life > config->pkinit_max_life_from_cert)
+ cp->max_life = config->pkinit_max_life_from_cert;
+ }
+
+ ret = hx509_cert_get_base_subject(r->context->hx509ctx,
+ cp->cert,
+ &name);
+ if (ret)
+ return ret;
+
+ ret = hx509_name_to_string(name, subject_name);
+ hx509_name_free(&name);
+ if (ret)
+ return ret;
+
+ kdc_log(r->context, config, 0,
+ "Trying to authorize PKINIT subject DN %s",
+ *subject_name);
+
+ ret = hdb_entry_get_pkinit_cert(client, &pc);
+ if (ret == 0 && pc) {
+ hx509_cert cert;
+ size_t j;
+
+ for (j = 0; j < pc->len; j++) {
+ cert = hx509_cert_init_data(r->context->hx509ctx,
+ pc->val[j].cert.data,
+ pc->val[j].cert.length,
+ NULL);
+ if (cert == NULL)
+ continue;
+ ret = hx509_cert_cmp(cert, cp->cert);
+ hx509_cert_free(cert);
+ if (ret == 0) {
+ kdc_log(r->context, config, 5,
+ "Found matching PKINIT cert in hdb");
+ return 0;
+ }
+ }
+ }
+
+
+ if (config->pkinit_princ_in_cert) {
+ ret = match_rfc_san(r->context, config,
+ r->context->hx509ctx,
+ cp->cert,
+ client->principal);
+ if (ret == 0) {
+ kdc_log(r->context, config, 5,
+ "Found matching PKINIT SAN in certificate");
+ return 0;
+ }
+ ret = match_ms_upn_san(r->context, config,
+ r->context->hx509ctx,
+ cp->cert,
+ clientdb,
+ client);
+ if (ret == 0) {
+ kdc_log(r->context, config, 5,
+ "Found matching MS UPN SAN in certificate");
+ return 0;
+ }
+ }
+
+ ret = hdb_entry_get_pkinit_acl(client, &acl);
+ if (ret == 0 && acl != NULL) {
+ /*
+ * Cheat here and compare the generated name with the string
+ * and not the reverse.
+ */
+ for (i = 0; i < acl->len; i++) {
+ if (strcmp(*subject_name, acl->val[0].subject) != 0)
+ continue;
+
+ /* Don't support issuer and anchor checking right now */
+ if (acl->val[0].issuer)
+ continue;
+ if (acl->val[0].anchor)
+ continue;
+
+ kdc_log(r->context, config, 5,
+ "Found matching PKINIT database ACL");
+ return 0;
+ }
+ }
+
+ for (i = 0; i < principal_mappings.len; i++) {
+ krb5_boolean b;
+
+ b = krb5_principal_compare(r->context,
+ client->principal,
+ principal_mappings.val[i].principal);
+ if (b == FALSE)
+ continue;
+ if (strcmp(principal_mappings.val[i].subject, *subject_name) != 0)
+ continue;
+ kdc_log(r->context, config, 5,
+ "Found matching PKINIT FILE ACL");
+ return 0;
+ }
+
+ ret = KRB5_KDC_ERR_CLIENT_NAME_MISMATCH;
+ krb5_set_error_message(r->context, ret,
+ "PKINIT no matching principals for %s",
+ *subject_name);
+
+ kdc_log(r->context, config, 5,
+ "PKINIT no matching principals for %s",
+ *subject_name);
+
+ free(*subject_name);
+ *subject_name = NULL;
+
+ return ret;
+}
+
+krb5_error_code
+_kdc_pk_validate_freshness_token(astgs_request_t r,
+ pk_client_params *cp)
+{
+ krb5_error_code ret = 0;
+ uint8_t *token_data = NULL;
+ size_t token_len;
+ uint8_t *remaining_token_data = NULL;
+ size_t remaining_len;
+ EncryptedData enc_data;
+ size_t size;
+ const hdb_entry *krbtgt = NULL;
+ krb5_kvno kvno;
+ const Keys *keys = NULL;
+ Key *key = NULL;
+ krb5_crypto crypto;
+ krb5_data ts_data;
+ PA_ENC_TS_ENC ts_enc;
+ long time_diff;
+
+ if (cp->freshness_token == NULL) {
+ if (r->config->require_pkinit_freshness) {
+ ret = KRB5KDC_ERR_PREAUTH_FAILED;
+ kdc_log(r->context, r->config, 0, "PKINIT request is missing required freshness token");
+ }
+
+ return ret;
+ }
+
+ token_data = cp->freshness_token->data;
+ token_len = cp->freshness_token->length;
+
+ /* Ensure that the token be not empty. */
+ if (token_data == NULL) {
+ kdc_log(r->context, r->config, 0, "Got empty freshness token");
+ return KRB5KDC_ERR_PREAUTH_FAILED;
+ }
+
+ /* Ensure that the two leading bytes are zero. */
+ if (token_len < 2 || token_data[0] || token_data[1]) {
+ kdc_log(r->context, r->config, 0, "Freshness token contains invalid data");
+ return KRB5KRB_AP_ERR_MODIFIED;
+ }
+
+ /* Decrypt the freshness token. */
+
+ remaining_token_data = token_data + 2;
+ remaining_len = token_len - 2;
+
+ ret = decode_EncryptedData(remaining_token_data, remaining_len, &enc_data, &size);
+ if (ret) {
+ kdc_log(r->context, r->config, 0, "Failed to decode freshness token");
+ return KRB5KRB_AP_ERR_MODIFIED;
+ }
+ if (size != remaining_len) {
+ kdc_log(r->context, r->config, 0, "Trailing data in EncryptedData of freshness token");
+ free_EncryptedData(&enc_data);
+ return KRB5KRB_AP_ERR_MODIFIED;
+ }
+
+ krbtgt = (r->krbtgt != NULL) ? r->krbtgt : r->server;
+ kvno = (enc_data.kvno != NULL) ? *enc_data.kvno : 0;
+
+ /* We will only accept freshness tokens signed by our local krbtgt. */
+ keys = hdb_kvno2keys(r->context, krbtgt, kvno);
+ if (keys == NULL) {
+ kdc_log(r->context, r->config, 0,
+ "No key with kvno %"PRId32" to decrypt freshness token",
+ kvno);
+ free_EncryptedData(&enc_data);
+ return KRB5KDC_ERR_PREAUTH_FAILED;
+ }
+
+ ret = hdb_enctype2key(r->context, r->client, keys,
+ enc_data.etype, &key);
+ if (ret) {
+ kdc_log(r->context, r->config, 0,
+ "No key with kvno %"PRId32", enctype %d to decrypt freshness token",
+ kvno, enc_data.etype);
+ free_EncryptedData(&enc_data);
+ return KRB5KDC_ERR_PREAUTH_FAILED;
+ }
+
+ ret = krb5_crypto_init(r->context, &key->key, 0, &crypto);
+ if (ret) {
+ const char *msg = krb5_get_error_message(r->context, ret);
+ kdc_log(r->context, r->config, 0,
+ "While attempting to decrypt freshness token, krb5_crypto_init failed: %s", msg);
+ krb5_free_error_message(r->context, msg);
+
+ free_EncryptedData(&enc_data);
+ return ret;
+ }
+
+ ret = krb5_decrypt_EncryptedData(r->context,
+ crypto,
+ KRB5_KU_AS_FRESHNESS,
+ &enc_data,
+ &ts_data);
+ krb5_crypto_destroy(r->context, crypto);
+ free_EncryptedData(&enc_data);
+ if (ret) {
+ kdc_log(r->context, r->config, 0, "Failed to decrypt freshness token");
+
+ free_EncryptedData(&enc_data);
+ return KRB5KRB_AP_ERR_MODIFIED;
+ }
+
+ /* Decode the timestamp. */
+
+ ret = decode_PA_ENC_TS_ENC(ts_data.data,
+ ts_data.length,
+ &ts_enc,
+ &size);
+ if (ret) {
+ kdc_log(r->context, r->config, 0, "Failed to decode PA-ENC-TS-ENC in freshness token");
+ krb5_data_free(&ts_data);
+ return KRB5KRB_AP_ERR_MODIFIED;
+ }
+ if (size != ts_data.length) {
+ kdc_log(r->context, r->config, 0, "Trailing data in PA-ENC-TS-ENC of freshness token");
+ free_PA_ENC_TS_ENC(&ts_enc);
+ krb5_data_free(&ts_data);
+ return KRB5KRB_AP_ERR_MODIFIED;
+ }
+ krb5_data_free(&ts_data);
+
+ time_diff = labs(kdc_time - ts_enc.patimestamp);
+ if (time_diff > r->context->max_skew) {
+ char token_time[100];
+
+ krb5_format_time(r->context, ts_enc.patimestamp,
+ token_time, sizeof(token_time), TRUE);
+
+ kdc_log(r->context, r->config, 4, "Freshness token has too large time skew: "
+ "time in token %s is out by %ld > %jd seconds — %s",
+ token_time,
+ time_diff,
+ (intmax_t)(r->context->max_skew),
+ r->cname);
+
+ r->e_text = NULL;
+ free_PA_ENC_TS_ENC(&ts_enc);
+ return KRB5_KDC_ERR_PREAUTH_EXPIRED;
+ }
+
+ free_PA_ENC_TS_ENC(&ts_enc);
+ return 0;
+}
+
+static krb5_error_code
+add_principal_mapping(krb5_context context,
+ const char *principal_name,
+ const char * subject)
+{
+ struct pk_allowed_princ *tmp;
+ krb5_principal principal;
+ krb5_error_code ret;
+
+ tmp = realloc(principal_mappings.val,
+ (principal_mappings.len + 1) * sizeof(*tmp));
+ if (tmp == NULL)
+ return ENOMEM;
+ principal_mappings.val = tmp;
+
+ ret = krb5_parse_name(context, principal_name, &principal);
+ if (ret)
+ return ret;
+
+ principal_mappings.val[principal_mappings.len].principal = principal;
+
+ principal_mappings.val[principal_mappings.len].subject = strdup(subject);
+ if (principal_mappings.val[principal_mappings.len].subject == NULL) {
+ krb5_free_principal(context, principal);
+ return ENOMEM;
+ }
+ principal_mappings.len++;
+
+ return 0;
+}
+
+krb5_error_code
+_kdc_add_initial_verified_cas(krb5_context context,
+ krb5_kdc_configuration *config,
+ pk_client_params *cp,
+ EncTicketPart *tkt)
+{
+ AD_INITIAL_VERIFIED_CAS cas;
+ krb5_error_code ret;
+ krb5_data data;
+ size_t size = 0;
+
+ memset(&cas, 0, sizeof(cas));
+
+ /* XXX add CAs to cas here */
+
+ ASN1_MALLOC_ENCODE(AD_INITIAL_VERIFIED_CAS, data.data, data.length,
+ &cas, &size, ret);
+ if (ret)
+ return ret;
+ if (data.length != size)
+ krb5_abortx(context, "internal asn.1 encoder error");
+
+ ret = _kdc_tkt_add_if_relevant_ad(context, tkt,
+ KRB5_AUTHDATA_INITIAL_VERIFIED_CAS,
+ &data);
+ krb5_data_free(&data);
+ return ret;
+}
+
+/*
+ *
+ */
+
+static void
+load_mappings(krb5_context context, const char *fn)
+{
+ krb5_error_code ret;
+ char buf[1024];
+ unsigned long lineno = 0;
+ FILE *f;
+
+ f = fopen(fn, "r");
+ if (f == NULL)
+ return;
+
+ while (fgets(buf, sizeof(buf), f) != NULL) {
+ char *subject_name, *p;
+
+ buf[strcspn(buf, "\n")] = '\0';
+ lineno++;
+
+ p = buf + strspn(buf, " \t");
+
+ if (*p == '#' || *p == '\0')
+ continue;
+
+ subject_name = strchr(p, ':');
+ if (subject_name == NULL) {
+ krb5_warnx(context, "pkinit mapping file line %lu "
+ "missing \":\" :%s",
+ lineno, buf);
+ continue;
+ }
+ *subject_name++ = '\0';
+
+ ret = add_principal_mapping(context, p, subject_name);
+ if (ret) {
+ krb5_warn(context, ret, "failed to add line %lu \":\" :%s\n",
+ lineno, buf);
+ continue;
+ }
+ }
+
+ fclose(f);
+}
+
+/*
+ *
+ */
+
+KDC_LIB_FUNCTION krb5_error_code KDC_LIB_CALL
+krb5_kdc_pk_initialize(krb5_context context,
+ krb5_kdc_configuration *config,
+ const char *user_id,
+ const char *anchors,
+ char **pool,
+ char **revoke_list)
+{
+ const char *file;
+ char *fn = NULL;
+ krb5_error_code ret;
+
+ file = krb5_config_get_string(context, NULL,
+ "libdefaults", "moduli", NULL);
+
+ ret = _krb5_parse_moduli(context, file, &moduli);
+ if (ret)
+ krb5_err(context, 1, ret, "PKINIT: failed to load moduli file");
+
+ principal_mappings.len = 0;
+ principal_mappings.val = NULL;
+
+ ret = _krb5_pk_load_id(context,
+ &kdc_identity,
+ user_id,
+ anchors,
+ pool,
+ revoke_list,
+ NULL,
+ NULL,
+ NULL);
+ if (ret) {
+ krb5_warn(context, ret, "PKINIT: failed to load ID");
+ config->enable_pkinit = 0;
+ return ret;
+ }
+
+ {
+ hx509_query *q;
+ hx509_cert cert;
+
+ ret = hx509_query_alloc(context->hx509ctx, &q);
+ if (ret) {
+ krb5_warnx(context, "PKINIT: out of memory");
+ return ENOMEM;
+ }
+
+ hx509_query_match_option(q, HX509_QUERY_OPTION_PRIVATE_KEY);
+ if (config->pkinit_kdc_friendly_name)
+ hx509_query_match_friendly_name(q, config->pkinit_kdc_friendly_name);
+
+ ret = hx509_certs_find(context->hx509ctx,
+ kdc_identity->certs,
+ q,
+ &cert);
+ hx509_query_free(context->hx509ctx, q);
+ if (ret == 0) {
+ if (hx509_cert_check_eku(context->hx509ctx, cert,
+ &asn1_oid_id_pkkdcekuoid, 0)) {
+ hx509_name name;
+ char *str;
+ ret = hx509_cert_get_subject(cert, &name);
+ if (ret == 0) {
+ hx509_name_to_string(name, &str);
+ krb5_warnx(context, "WARNING Found KDC certificate (%s) "
+ "is missing the PKINIT KDC EKU, this is bad for "
+ "interoperability.", str);
+ hx509_name_free(&name);
+ free(str);
+ }
+ }
+ hx509_cert_free(cert);
+ } else
+ krb5_warnx(context, "PKINIT: failed to find a signing "
+ "certificate with a public key");
+ }
+
+ if (krb5_config_get_bool_default(context,
+ NULL,
+ FALSE,
+ "kdc",
+ "pkinit_allow_proxy_certificate",
+ NULL))
+ config->pkinit_allow_proxy_certs = 1;
+
+ file = krb5_config_get_string(context,
+ NULL,
+ "kdc",
+ "pkinit_mappings_file",
+ NULL);
+ if (file == NULL) {
+ int aret;
+
+ aret = asprintf(&fn, "%s/pki-mapping", hdb_db_dir(context));
+ if (aret == -1) {
+ krb5_warnx(context, "PKINIT: out of memory");
+ return ENOMEM;
+ }
+
+ file = fn;
+ }
+
+ load_mappings(context, file);
+ if (fn)
+ free(fn);
+
+ return 0;
+}
+
+#endif /* PKINIT */
diff --git a/third_party/heimdal/kdc/process.c b/third_party/heimdal/kdc/process.c
new file mode 100644
index 0000000..b53d91f
--- /dev/null
+++ b/third_party/heimdal/kdc/process.c
@@ -0,0 +1,611 @@
+/*
+ * Copyright (c) 1997-2005 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "kdc_locl.h"
+#include <vis.h>
+
+/*
+ *
+ */
+
+#undef __attribute__
+#define __attribute__(x)
+
+KDC_LIB_FUNCTION void KDC_LIB_CALL
+kdc_audit_vaddreason(kdc_request_t r, const char *fmt, va_list ap)
+ __attribute__ ((__format__ (__printf__, 2, 0)))
+{
+ heim_audit_vaddreason((heim_svc_req_desc)r, fmt, ap);
+}
+
+KDC_LIB_FUNCTION void KDC_LIB_CALL
+kdc_audit_addreason(kdc_request_t r, const char *fmt, ...)
+ __attribute__ ((__format__ (__printf__, 2, 3)))
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ heim_audit_vaddreason((heim_svc_req_desc)r, fmt, ap);
+ va_end(ap);
+}
+
+/*
+ * append_token adds a token which is optionally a kv-pair and it
+ * also optionally eats the whitespace. If k == NULL, then it's
+ * not a kv-pair.
+ */
+
+KDC_LIB_FUNCTION void KDC_LIB_CALL
+kdc_audit_vaddkv(kdc_request_t r, int flags, const char *k,
+ const char *fmt, va_list ap)
+ __attribute__ ((__format__ (__printf__, 4, 0)))
+{
+ heim_audit_vaddkv((heim_svc_req_desc)r, flags, k, fmt, ap);
+}
+
+KDC_LIB_FUNCTION void KDC_LIB_CALL
+kdc_audit_addkv(kdc_request_t r, int flags, const char *k,
+ const char *fmt, ...)
+ __attribute__ ((__format__ (__printf__, 4, 5)))
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ heim_audit_vaddkv((heim_svc_req_desc)r, flags, k, fmt, ap);
+ va_end(ap);
+}
+
+KDC_LIB_FUNCTION void KDC_LIB_CALL
+kdc_audit_addkv_timediff(kdc_request_t r, const char *k,
+ const struct timeval *start,
+ const struct timeval *end)
+{
+ heim_audit_addkv_timediff((heim_svc_req_desc)r,k, start, end);
+}
+
+KDC_LIB_FUNCTION void KDC_LIB_CALL
+kdc_audit_setkv_bool(kdc_request_t r, const char *k, krb5_boolean v)
+{
+ heim_audit_setkv_bool((heim_svc_req_desc)r, k, (int)v);
+}
+
+KDC_LIB_FUNCTION void KDC_LIB_CALL
+kdc_audit_addkv_number(kdc_request_t r, const char *k, int64_t v)
+{
+ heim_audit_addkv_number((heim_svc_req_desc)r, k, v);
+}
+
+KDC_LIB_FUNCTION void KDC_LIB_CALL
+kdc_audit_setkv_number(kdc_request_t r, const char *k, int64_t v)
+{
+ heim_audit_setkv_number((heim_svc_req_desc)r, k, v);
+}
+
+KDC_LIB_FUNCTION void KDC_LIB_CALL
+kdc_audit_addkv_object(kdc_request_t r, const char *k, kdc_object_t obj)
+{
+ heim_audit_addkv_object((heim_svc_req_desc)r, k, obj);
+}
+
+KDC_LIB_FUNCTION void KDC_LIB_CALL
+kdc_audit_setkv_object(kdc_request_t r, const char *k, kdc_object_t obj)
+{
+ heim_audit_setkv_object((heim_svc_req_desc)r, k, obj);
+}
+
+KDC_LIB_FUNCTION kdc_object_t KDC_LIB_CALL
+kdc_audit_getkv(kdc_request_t r, const char *k)
+{
+ return heim_audit_getkv((heim_svc_req_desc)r, k);
+}
+
+/*
+ * Add up to 3 key value pairs to record HostAddresses from request body or
+ * PA-TGS ticket or whatever.
+ */
+KDC_LIB_FUNCTION void KDC_LIB_CALL
+kdc_audit_addaddrs(kdc_request_t r, HostAddresses *a, const char *key)
+{
+ size_t i;
+ char buf[128];
+
+ if (a->len > 3) {
+ char numkey[32];
+
+ if (snprintf(numkey, sizeof(numkey), "num%s", key) >= sizeof(numkey))
+ numkey[31] = '\0';
+ kdc_audit_addkv(r, 0, numkey, "%llu", (unsigned long long)a->len);
+ }
+
+ for (i = 0; i < 3 && i < a->len; i++) {
+ if (krb5_print_address(&a->val[i], buf, sizeof(buf), NULL) == 0)
+ kdc_audit_addkv(r, 0, key, "%s", buf);
+ }
+}
+
+KDC_LIB_FUNCTION void KDC_LIB_CALL
+_kdc_audit_trail(kdc_request_t r, krb5_error_code ret)
+{
+ const char *retname = NULL;
+
+ /* Get a symbolic name for some error codes */
+#define CASE(x) case x : retname = #x; break
+ switch (ret ? ret : r->error_code) {
+ CASE(ENOMEM);
+ CASE(EACCES);
+ CASE(HDB_ERR_NOT_FOUND_HERE);
+ CASE(HDB_ERR_WRONG_REALM);
+ CASE(HDB_ERR_EXISTS);
+ CASE(HDB_ERR_KVNO_NOT_FOUND);
+ CASE(HDB_ERR_NOENTRY);
+ CASE(HDB_ERR_NO_MKEY);
+ CASE(KRB5KDC_ERR_BADOPTION);
+ CASE(KRB5KDC_ERR_CANNOT_POSTDATE);
+ CASE(KRB5KDC_ERR_CLIENT_NOTYET);
+ CASE(KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN);
+ CASE(KRB5KDC_ERR_ETYPE_NOSUPP);
+ CASE(KRB5KDC_ERR_KEY_EXPIRED);
+ CASE(KRB5KDC_ERR_NAME_EXP);
+ CASE(KRB5KDC_ERR_NEVER_VALID);
+ CASE(KRB5KDC_ERR_NONE);
+ CASE(KRB5KDC_ERR_NULL_KEY);
+ CASE(KRB5KDC_ERR_PADATA_TYPE_NOSUPP);
+ CASE(KRB5KDC_ERR_POLICY);
+ CASE(KRB5KDC_ERR_PREAUTH_FAILED);
+ CASE(KRB5KDC_ERR_PREAUTH_REQUIRED);
+ CASE(KRB5KDC_ERR_SERVER_NOMATCH);
+ CASE(KRB5KDC_ERR_SERVICE_EXP);
+ CASE(KRB5KDC_ERR_SERVICE_NOTYET);
+ CASE(KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN);
+ CASE(KRB5KDC_ERR_TRTYPE_NOSUPP);
+ CASE(KRB5KRB_AP_ERR_BADADDR);
+ CASE(KRB5KRB_AP_ERR_BADDIRECTION);
+ CASE(KRB5KRB_AP_ERR_BAD_INTEGRITY);
+ CASE(KRB5KRB_AP_ERR_BADKEYVER);
+ CASE(KRB5KRB_AP_ERR_BADMATCH);
+ CASE(KRB5KRB_AP_ERR_BADORDER);
+ CASE(KRB5KRB_AP_ERR_BADSEQ);
+ CASE(KRB5KRB_AP_ERR_BADVERSION);
+ CASE(KRB5KRB_AP_ERR_ILL_CR_TKT);
+ CASE(KRB5KRB_AP_ERR_INAPP_CKSUM);
+ CASE(KRB5KRB_AP_ERR_METHOD);
+ CASE(KRB5KRB_AP_ERR_MODIFIED);
+ CASE(KRB5KRB_AP_ERR_MSG_TYPE);
+ CASE(KRB5KRB_AP_ERR_MUT_FAIL);
+ CASE(KRB5KRB_AP_ERR_NOKEY);
+ CASE(KRB5KRB_AP_ERR_NOT_US);
+ CASE(KRB5KRB_AP_ERR_REPEAT);
+ CASE(KRB5KRB_AP_ERR_SKEW);
+ CASE(KRB5KRB_AP_ERR_TKT_EXPIRED);
+ CASE(KRB5KRB_AP_ERR_TKT_INVALID);
+ CASE(KRB5KRB_AP_ERR_TKT_NYV);
+ CASE(KRB5KRB_AP_ERR_V4_REPLY);
+ CASE(KRB5KRB_AP_PATH_NOT_ACCEPTED);
+ CASE(KRB5KRB_AP_WRONG_PRINC);
+ CASE(KRB5KRB_ERR_FIELD_TOOLONG);
+ CASE(KRB5KRB_ERR_GENERIC);
+ CASE(KRB5KRB_ERR_RESPONSE_TOO_BIG);
+
+ case 0:
+ retname = "SUCCESS";
+ break;
+ default:
+ retname = NULL;
+ break;
+ }
+
+ /* Let's save a few bytes */
+#define PREFIX "KRB5KDC_"
+ if (retname && strncmp(PREFIX, retname, strlen(PREFIX)) == 0)
+ retname += strlen(PREFIX);
+#undef PREFIX
+
+ heim_audit_trail((heim_svc_req_desc)r, ret, retname);
+}
+
+KDC_LIB_FUNCTION void KDC_LIB_CALL
+krb5_kdc_update_time(struct timeval *tv)
+{
+ if (tv == NULL)
+ gettimeofday(&_kdc_now, NULL);
+ else
+ _kdc_now = *tv;
+}
+
+KDC_LIB_FUNCTION struct timeval KDC_LIB_CALL
+krb5_kdc_get_time(void)
+{
+ return _kdc_now;
+}
+
+
+#define EXTEND_REQUEST_T(LHS, RHS) do { \
+ RHS = realloc(LHS, sizeof(*RHS)); \
+ if (!RHS) \
+ return krb5_enomem((LHS)->context); \
+ LHS = (void *)RHS; \
+ memset(((char *)LHS) + sizeof(*LHS), \
+ 0x0, \
+ sizeof(*RHS) - sizeof(*LHS)); \
+ } while (0)
+
+static krb5_error_code
+kdc_as_req(kdc_request_t *rptr, int *claim)
+{
+ astgs_request_t r;
+ krb5_error_code ret;
+ size_t len;
+
+ /* We must free things in the extensions */
+ EXTEND_REQUEST_T(*rptr, r);
+
+ ret = decode_AS_REQ(r->request.data, r->request.length, &r->req, &len);
+ if (ret)
+ return ret;
+
+ r->reqtype = "AS-REQ";
+ r->use_request_t = 1;
+ *claim = 1;
+
+ ret = _kdc_as_rep(r);
+ free_AS_REQ(&r->req);
+ return ret;
+}
+
+
+static krb5_error_code
+kdc_tgs_req(kdc_request_t *rptr, int *claim)
+{
+ astgs_request_t r;
+ krb5_error_code ret;
+ size_t len;
+
+ /* We must free things in the extensions */
+ EXTEND_REQUEST_T(*rptr, r);
+
+ ret = decode_TGS_REQ(r->request.data, r->request.length, &r->req, &len);
+ if (ret)
+ return ret;
+
+ r->reqtype = "TGS-REQ";
+ r->use_request_t = 1;
+ *claim = 1;
+
+ ret = _kdc_tgs_rep(r);
+ free_TGS_REQ(&r->req);
+ return ret;
+}
+
+#ifdef DIGEST
+
+static krb5_error_code
+kdc_digest(kdc_request_t *rptr, int *claim)
+{
+ kdc_request_t r;
+ DigestREQ digestreq;
+ krb5_error_code ret;
+ size_t len;
+
+ r = *rptr;
+
+ ret = decode_DigestREQ(r->request.data, r->request.length,
+ &digestreq, &len);
+ if (ret)
+ return ret;
+
+ r->use_request_t = 0;
+ *claim = 1;
+
+ ret = _kdc_do_digest(r->context, r->config, &digestreq,
+ r->reply, r->from, r->addr);
+ free_DigestREQ(&digestreq);
+ return ret;
+}
+
+#endif
+
+#ifdef KX509
+
+static krb5_error_code
+kdc_kx509(kdc_request_t *rptr, int *claim)
+{
+ kx509_req_context r;
+ krb5_error_code ret;
+
+ /* We must free things in the extensions */
+ EXTEND_REQUEST_T(*rptr, r);
+
+ ret = _kdc_try_kx509_request(r);
+ if (ret)
+ return ret;
+
+ r->use_request_t = 1;
+ r->reqtype = "KX509";
+ *claim = 1;
+
+ return _kdc_do_kx509(r); /* Must clean up the req struct extensions */
+}
+
+#endif
+
+
+static struct krb5_kdc_service services[] = {
+ { KS_KRB5, "AS-REQ", kdc_as_req },
+ { KS_KRB5, "TGS-REQ", kdc_tgs_req },
+#ifdef DIGEST
+ { 0, "DIGEST", kdc_digest },
+#endif
+#ifdef KX509
+ { 0, "KX509", kdc_kx509 },
+#endif
+ { 0, NULL, NULL }
+};
+
+static int
+process_request(krb5_context context,
+ krb5_kdc_configuration *config,
+ unsigned int krb5_only,
+ unsigned char *buf,
+ size_t len,
+ krb5_data *reply,
+ krb5_boolean *prependlength,
+ const char *from,
+ struct sockaddr *addr,
+ int datagram_reply)
+{
+ kdc_request_t r;
+ krb5_error_code ret;
+ unsigned int i;
+ int claim = 0;
+
+ r = calloc(sizeof(*r), 1);
+ if (!r)
+ return krb5_enomem(context);
+
+ r->context = context;
+ r->hcontext = context->hcontext;
+ r->config = config;
+ r->logf = config->logf;
+ r->from = from;
+ r->addr = addr;
+ r->request.data = buf;
+ r->request.length = len;
+ r->datagram_reply = datagram_reply;
+ r->reply = reply;
+ r->kv = heim_dict_create(10);
+ r->attributes = heim_dict_create(1);
+ if (r->kv == NULL || r->attributes == NULL) {
+ heim_release(r->kv);
+ heim_release(r->attributes);
+ free(r);
+ return krb5_enomem(context);
+ }
+
+ gettimeofday(&r->tv_start, NULL);
+
+ for (i = 0; services[i].process != NULL; i++) {
+ if (krb5_only && (services[i].flags & KS_KRB5) == 0)
+ continue;
+ kdc_log(context, config, 7, "Probing for %s", services[i].name);
+ ret = (*services[i].process)(&r, &claim);
+ if (claim) {
+ if (prependlength && services[i].flags & KS_NO_LENGTH)
+ *prependlength = 0;
+
+ if (r->use_request_t) {
+ gettimeofday(&r->tv_end, NULL);
+ _kdc_audit_trail(r, ret);
+ free(r->cname);
+ free(r->sname);
+ free(r->e_text_buf);
+ krb5_data_free(&r->e_data);
+ }
+
+ heim_release(r->reason);
+ heim_release(r->kv);
+ heim_release(r->attributes);
+ free(r);
+ return ret;
+ }
+ }
+
+ heim_release(r->reason);
+ heim_release(r->kv);
+ heim_release(r->attributes);
+ free(r);
+ return -1;
+}
+
+/*
+ * handle the request in `buf, len', from `addr' (or `from' as a string),
+ * sending a reply in `reply'.
+ */
+
+KDC_LIB_FUNCTION int KDC_LIB_CALL
+krb5_kdc_process_request(krb5_context context,
+ krb5_kdc_configuration *config,
+ unsigned char *buf,
+ size_t len,
+ krb5_data *reply,
+ krb5_boolean *prependlength,
+ const char *from,
+ struct sockaddr *addr,
+ int datagram_reply)
+{
+ return process_request(context, config, 0, buf, len, reply, prependlength,
+ from, addr, datagram_reply);
+}
+
+/*
+ * handle the request in `buf, len', from `addr' (or `from' as a string),
+ * sending a reply in `reply'.
+ *
+ * This only processes krb5 requests
+ */
+
+KDC_LIB_FUNCTION int KDC_LIB_CALL
+krb5_kdc_process_krb5_request(krb5_context context,
+ krb5_kdc_configuration *config,
+ unsigned char *buf,
+ size_t len,
+ krb5_data *reply,
+ const char *from,
+ struct sockaddr *addr,
+ int datagram_reply)
+{
+ return process_request(context, config, 1, buf, len, reply, NULL,
+ from, addr, datagram_reply);
+}
+
+
+/*
+ *
+ */
+
+KDC_LIB_FUNCTION int KDC_LIB_CALL
+krb5_kdc_save_request(krb5_context context,
+ const char *fn,
+ const unsigned char *buf,
+ size_t len,
+ const krb5_data *reply,
+ const struct sockaddr *sa)
+{
+ krb5_storage *sp;
+ krb5_address a;
+ int fd = -1;
+ int ret = 0;
+ uint32_t t;
+ krb5_data d;
+
+ memset(&a, 0, sizeof(a));
+
+ d.data = rk_UNCONST(buf); /* do not free here */
+ d.length = len;
+ t = _kdc_now.tv_sec;
+
+ sp = krb5_storage_emem();
+ if (sp == NULL)
+ ret = krb5_enomem(context);
+
+ if (ret == 0)
+ ret = krb5_sockaddr2address(context, sa, &a);
+ if (ret == 0)
+ ret = krb5_store_uint32(sp, 1);
+ if (ret == 0)
+ ret = krb5_store_uint32(sp, t);
+ if (ret == 0)
+ ret = krb5_store_address(sp, a);
+ if (ret == 0)
+ ret = krb5_store_data(sp, d);
+ d.length = 0;
+ d.data = NULL;
+ if (ret == 0) {
+ Der_class cl;
+ Der_type ty;
+ unsigned int tag;
+ ret = der_get_tag (reply->data, reply->length,
+ &cl, &ty, &tag, NULL);
+ if (ret) {
+ ret = krb5_store_uint32(sp, 0xffffffff);
+ if (ret == 0)
+ ret = krb5_store_uint32(sp, 0xffffffff);
+ } else {
+ ret = krb5_store_uint32(sp, MAKE_TAG(cl, ty, 0));
+ if (ret == 0)
+ ret = krb5_store_uint32(sp, tag);
+ }
+ }
+
+ if (ret == 0)
+ ret = krb5_storage_to_data(sp, &d);
+ krb5_storage_free(sp);
+ sp = NULL;
+
+ /*
+ * We've got KDC concurrency, so we're going to try to do a single O_APPEND
+ * write(2). Hopefully we manage to write enough of the header that one
+ * can skip this request if it fails to write completely.
+ */
+ if (ret == 0)
+ fd = open(fn, O_WRONLY|O_CREAT|O_APPEND, 0600);
+ if (fd < 0)
+ krb5_set_error_message(context, ret = errno, "Failed to open: %s", fn);
+ if (ret == 0) {
+ sp = krb5_storage_from_fd(fd);
+ if (sp == NULL)
+ krb5_set_error_message(context, ret = ENOMEM,
+ "Storage failed to open fd");
+ }
+ (void) close(fd);
+ if (ret == 0)
+ ret = krb5_store_data(sp, d);
+ krb5_free_address(context, &a);
+ /*
+ * krb5_storage_free() currently always returns 0, but for FDs it sets
+ * errno to whatever close() set it to if it failed.
+ */
+ errno = 0;
+ if (ret == 0)
+ ret = krb5_storage_free(sp);
+ else
+ (void) krb5_storage_free(sp);
+ if (ret == 0 && errno)
+ ret = errno;
+
+ return ret;
+}
+
+KDC_LIB_FUNCTION krb5_error_code KDC_LIB_CALL
+kdc_request_set_attribute(kdc_request_t r, kdc_object_t key, kdc_object_t value)
+{
+ return heim_dict_set_value(r->attributes, key, value);
+}
+
+KDC_LIB_FUNCTION kdc_object_t KDC_LIB_CALL
+kdc_request_get_attribute(kdc_request_t r, kdc_object_t key)
+{
+ return heim_dict_get_value(r->attributes, key);
+}
+
+KDC_LIB_FUNCTION kdc_object_t KDC_LIB_CALL
+kdc_request_copy_attribute(kdc_request_t r, kdc_object_t key)
+{
+ return heim_dict_copy_value(r->attributes, key);
+}
+
+KDC_LIB_FUNCTION void KDC_LIB_CALL
+kdc_request_delete_attribute(kdc_request_t r, kdc_object_t key)
+{
+ heim_dict_delete_key(r->attributes, key);
+}
diff --git a/third_party/heimdal/kdc/set_dbinfo.c b/third_party/heimdal/kdc/set_dbinfo.c
new file mode 100644
index 0000000..683eaaf
--- /dev/null
+++ b/third_party/heimdal/kdc/set_dbinfo.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 1997-2007 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "kdc_locl.h"
+
+static krb5_error_code
+add_db(krb5_context context, struct krb5_kdc_configuration *c,
+ const char *conf, const char *master_key)
+{
+ krb5_error_code ret;
+ void *ptr;
+
+ ptr = realloc(c->db, (c->num_db + 1) * sizeof(*c->db));
+ if (ptr == NULL) {
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+ c->db = ptr;
+
+ ret = hdb_create(context, &c->db[c->num_db], conf);
+ if(ret)
+ return ret;
+
+ c->num_db++;
+
+ if (master_key) {
+ ret = hdb_set_master_keyfile(context, c->db[c->num_db - 1], master_key);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+KDC_LIB_FUNCTION krb5_error_code KDC_LIB_CALL
+krb5_kdc_set_dbinfo(krb5_context context, struct krb5_kdc_configuration *c)
+{
+ struct hdb_dbinfo *info, *d;
+ krb5_error_code ret;
+ int i;
+
+ /* fetch the databases */
+ ret = hdb_get_dbinfo(context, &info);
+ if (ret)
+ return ret;
+
+ d = NULL;
+ while ((d = hdb_dbinfo_get_next(info, d)) != NULL) {
+
+ ret = add_db(context, c,
+ hdb_dbinfo_get_dbname(context, d),
+ hdb_dbinfo_get_mkey_file(context, d));
+ if (ret)
+ goto out;
+
+ kdc_log(context, c, 3, "label: %s",
+ hdb_dbinfo_get_label(context, d));
+ kdc_log(context, c, 3, "\tdbname: %s",
+ hdb_dbinfo_get_dbname(context, d));
+ kdc_log(context, c, 3, "\tmkey_file: %s",
+ hdb_dbinfo_get_mkey_file(context, d));
+ kdc_log(context, c, 3, "\tacl_file: %s",
+ hdb_dbinfo_get_acl_file(context, d));
+ }
+ hdb_free_dbinfo(context, &info);
+
+ return 0;
+out:
+ for (i = 0; i < c->num_db; i++)
+ if (c->db[i] && c->db[i]->hdb_destroy)
+ (*c->db[i]->hdb_destroy)(context, c->db[i]);
+ c->num_db = 0;
+ free(c->db);
+ c->db = NULL;
+
+ hdb_free_dbinfo(context, &info);
+
+ return ret;
+}
+
+
diff --git a/third_party/heimdal/kdc/string2key-version.rc b/third_party/heimdal/kdc/string2key-version.rc
new file mode 100644
index 0000000..120ef4b
--- /dev/null
+++ b/third_party/heimdal/kdc/string2key-version.rc
@@ -0,0 +1,36 @@
+/***********************************************************************
+ * Copyright (c) 2010, Secure Endpoints Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ **********************************************************************/
+
+#define RC_FILE_TYPE VFT_APP
+#define RC_FILE_DESC_0409 "Password to Key Mapper"
+#define RC_FILE_ORIG_0409 "string2key.exe"
+
+#include "../windows/version.rc"
diff --git a/third_party/heimdal/kdc/string2key.8 b/third_party/heimdal/kdc/string2key.8
new file mode 100644
index 0000000..1b38d33
--- /dev/null
+++ b/third_party/heimdal/kdc/string2key.8
@@ -0,0 +1,85 @@
+.\" Copyright (c) 2000 - 2002 Kungliga Tekniska Högskolan
+.\" (Royal Institute of Technology, Stockholm, Sweden).
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\"
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\"
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" 3. Neither the name of the Institute nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $Id$
+.\"
+.Dd March 4, 2000
+.Dt STRING2KEY 8
+.Os HEIMDAL
+.Sh NAME
+.Nm string2key
+.Nd map a password into a key
+.Sh SYNOPSIS
+.Nm
+.Op Fl 5 | Fl Fl version5
+.Op Fl 4 | Fl Fl version4
+.Op Fl a | Fl Fl afs
+.Oo Fl c Ar cell \*(Ba Xo
+.Fl Fl cell= Ns Ar cell
+.Xc
+.Oc
+.Oo Fl w Ar password \*(Ba Xo
+.Fl Fl password= Ns Ar password
+.Xc
+.Oc
+.Oo Fl p Ar principal \*(Ba Xo
+.Fl Fl principal= Ns Ar principal
+.Xc
+.Oc
+.Oo Fl k Ar string \*(Ba Xo
+.Fl Fl keytype= Ns Ar string
+.Xc
+.Oc
+.Ar password
+.Sh DESCRIPTION
+.Nm
+performs the string-to-key function.
+This is useful when you want to handle the raw key instead of the password.
+Supported options:
+.Bl -tag -width Ds
+.It Fl 5 , Fl Fl version5
+Output Kerberos v5 string-to-key
+.It Fl 4 , Fl Fl version4
+Output Kerberos v4 string-to-key
+.It Fl a , Fl Fl afs
+Output AFS string-to-key
+.It Fl c Ar cell , Fl Fl cell= Ns Ar cell
+AFS cell to use
+.It Fl w Ar password , Fl Fl password= Ns Ar password
+Password to use
+.It Fl p Ar principal , Fl Fl principal= Ns Ar principal
+Kerberos v5 principal to use
+.It Fl k Ar string , Fl Fl keytype= Ns Ar string
+Keytype
+.It Fl Fl version
+print version
+.It Fl Fl help
+.El
diff --git a/third_party/heimdal/kdc/string2key.c b/third_party/heimdal/kdc/string2key.c
new file mode 100644
index 0000000..4b9c62e
--- /dev/null
+++ b/third_party/heimdal/kdc/string2key.c
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 1997-2003 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "headers.h"
+#include <getarg.h>
+
+int version5;
+int version4;
+int afs;
+char *principal;
+char *cell;
+char *password;
+const char *keytype_str = "des3-cbc-sha1";
+int version;
+int help;
+
+struct getargs args[] = {
+ { "version5", '5', arg_flag, &version5, "Output Kerberos v5 string-to-key",
+ NULL },
+ { "version4", '4', arg_flag, &version4, "Output Kerberos v4 string-to-key",
+ NULL },
+ { "afs", 'a', arg_flag, &afs, "Output AFS string-to-key", NULL },
+ { "cell", 'c', arg_string, &cell, "AFS cell to use", "cell" },
+ { "password", 'w', arg_string, &password, "Password to use", "password" },
+ { "principal",'p', arg_string, &principal, "Kerberos v5 principal to use", "principal" },
+ { "keytype", 'k', arg_string, rk_UNCONST(&keytype_str), "Keytype", NULL },
+ { "version", 0, arg_flag, &version, "print version", NULL },
+ { "help", 0, arg_flag, &help, NULL, NULL }
+};
+
+int num_args = sizeof(args) / sizeof(args[0]);
+
+static void
+usage(int status)
+{
+ arg_printusage (args, num_args, NULL, "password");
+ exit(status);
+}
+
+static void
+tokey(krb5_context context,
+ krb5_enctype enctype,
+ const char *pw,
+ krb5_salt salt,
+ const char *label)
+{
+ krb5_error_code ret;
+ size_t i;
+ krb5_keyblock key;
+ char *e;
+
+ ret = krb5_string_to_key_salt(context, enctype, pw, salt, &key);
+ if (ret)
+ krb5_err(context, 1, ret, "krb5_string_to_key_salt");
+ ret = krb5_enctype_to_string(context, enctype, &e);
+ if (ret)
+ krb5_err(context, 1, ret, "krb5_enctype_to_string");
+ printf(label, e);
+ printf(": ");
+ for(i = 0; i < key.keyvalue.length; i++)
+ printf("%02x", ((unsigned char*)key.keyvalue.data)[i]);
+ printf("\n");
+ krb5_free_keyblock_contents(context, &key);
+ free(e);
+}
+
+int
+main(int argc, char **argv)
+{
+ krb5_context context;
+ krb5_principal princ;
+ krb5_salt salt;
+ int optidx;
+ char buf[1024];
+ krb5_enctype etype;
+ krb5_error_code ret;
+
+ optidx = krb5_program_setup(&context, argc, argv, args, num_args, NULL);
+
+ if(help)
+ usage(0);
+
+ if(version){
+ print_version (NULL);
+ return 0;
+ }
+
+ argc -= optidx;
+ argv += optidx;
+
+ if (argc > 1)
+ usage(1);
+
+ if(!version5 && !version4 && !afs)
+ version5 = 1;
+
+ ret = krb5_string_to_enctype(context, keytype_str, &etype);
+ if(ret)
+ krb5_err(context, 1, ret, "krb5_string_to_enctype");
+
+ if((etype != ETYPE_DES_CBC_CRC &&
+ etype != ETYPE_DES_CBC_MD4 &&
+ etype != ETYPE_DES_CBC_MD5) &&
+ (afs || version4)) {
+ if(!version5) {
+ etype = ETYPE_DES_CBC_CRC;
+ } else {
+ krb5_errx(context, 1,
+ "DES is the only valid keytype for AFS and Kerberos 4");
+ }
+ }
+
+ if(version5 && principal == NULL){
+ printf("Kerberos v5 principal: ");
+ if(fgets(buf, sizeof(buf), stdin) == NULL)
+ return 1;
+ buf[strcspn(buf, "\r\n")] = '\0';
+ principal = estrdup(buf);
+ }
+ if(afs && cell == NULL){
+ printf("AFS cell: ");
+ if(fgets(buf, sizeof(buf), stdin) == NULL)
+ return 1;
+ buf[strcspn(buf, "\r\n")] = '\0';
+ cell = estrdup(buf);
+ }
+ if(argv[0])
+ password = argv[0];
+ if(password == NULL){
+ if(UI_UTIL_read_pw_string(buf, sizeof(buf), "Password: ", 0))
+ return 1;
+ password = buf;
+ }
+
+ if(version5){
+ ret = krb5_parse_name(context, principal, &princ);
+ if (ret)
+ krb5_err(context, 1, ret, "failed to unparse name: %s", principal);
+ ret = krb5_get_pw_salt(context, princ, &salt);
+ if (ret)
+ krb5_err(context, 1, ret, "failed to get salt for %s", principal);
+
+ tokey(context, etype, password, salt, "Kerberos 5 (%s)");
+ krb5_free_salt(context, salt);
+ }
+ if(version4){
+ salt.salttype = KRB5_PW_SALT;
+ salt.saltvalue.length = 0;
+ salt.saltvalue.data = NULL;
+ tokey(context, ETYPE_DES_CBC_MD5, password, salt, "Kerberos 4");
+ }
+ if(afs){
+ salt.salttype = KRB5_AFS3_SALT;
+ salt.saltvalue.length = strlen(cell);
+ salt.saltvalue.data = cell;
+ tokey(context, ETYPE_DES_CBC_MD5, password, salt, "AFS");
+ }
+ return 0;
+}
diff --git a/third_party/heimdal/kdc/test_csr_authorizer.c b/third_party/heimdal/kdc/test_csr_authorizer.c
new file mode 100644
index 0000000..200af16
--- /dev/null
+++ b/third_party/heimdal/kdc/test_csr_authorizer.c
@@ -0,0 +1,597 @@
+/*
+ * Copyright (c) 2022 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include "kdc_locl.h"
+#include <heim-ipc.h>
+
+/*
+ * This program implements two things:
+ *
+ * - a utility for testing the `kdc_authorize_csr()' function and the plugins
+ * that uses,
+ *
+ * and
+ *
+ * - a server for the IPC authorizer.
+ *
+ * For the latter, requested certificate SANs and EKUs are authorized by
+ * checking for existence of files of the form:
+ *
+ * /<path>/<princ>/<ext>-<value>
+ *
+ * where <path> is given as an option.
+ *
+ * <princ> is a requesting client principal name with all characters other than
+ * alphanumeric, '-', '_', and non-leading '.' URL-encoded.
+ *
+ * <ext> is one of:
+ *
+ * - pkinit (SAN)
+ * - xmpp (SAN)
+ * - email (SAN)
+ * - ms-upn (SAN)
+ * - dnsname (SAN)
+ * - eku (EKU OID)
+ *
+ * and <value> is a display form of the SAN or EKU OID, with SANs URL-encoded
+ * just like principal names (see above).
+ *
+ * OIDs are of the form "1.2.3.4.5".
+ *
+ * Only digitalSignature and nonRepudiation key usage values are permitted.
+ */
+
+static int help_flag;
+static int version_flag;
+static int daemon_flag;
+static int daemon_child_flag = -1;
+static int ignore_flag = 0;
+static int server_flag = 0;
+static const char *app_string = "kdc";
+static const char *socket_dir;
+static const char *authz_dir;
+
+struct getargs args[] = {
+ { "help", 'h', arg_flag, &help_flag,
+ "Print usage message", NULL },
+ { "version", 'v', arg_flag, &version_flag,
+ "Print version", NULL },
+ { "app", 'a', arg_string, &app_string,
+ "App to test (kdc or bx509); default: kdc", "APPNAME" },
+ { "socket-dir", 'S', arg_string, &socket_dir,
+ "IPC socket directory", "DIR" },
+ { "authorization-dir", 'A', arg_string, &authz_dir,
+ "authorization directory", "DIR" },
+ { "server", '\0', arg_flag, &server_flag,
+ "Server mode", NULL },
+ { "ignore", 'I', arg_flag, &ignore_flag,
+ "ignore requests", NULL },
+ { "daemon", 'd', arg_flag, &daemon_flag,
+ "daemonize", NULL },
+ { "daemon-child", '\0', arg_flag, &daemon_child_flag,
+ "internal-use-only option", NULL },
+};
+size_t num_args = sizeof(args) / sizeof(args[0]);
+
+static int
+usage(int e)
+{
+ arg_printusage(args, num_args, NULL, "PATH-TO-DER-CSR PRINCIPAL");
+ fprintf(stderr,
+ "\tExercise CSR authorization plugins for a given CSR for a\n"
+ "\tgiven principal.\n\n"
+ "\tServer-mode (--server) looks for files in the \n"
+ "\t--authorization-dir DIR directory named:\n"
+ "\n"
+ "\t\teku=OID\n"
+ "\t\tsan_pkinit=PRINCIPAL\n"
+ "\t\tsan_ms_upn=PRINCIPAL\n"
+ "\t\tsan_dnsname=DOMAINNAME\n"
+ "\t\tsan_xmpp=JABBER-ID\n"
+ "\t\tsan_email=EMAIL\n"
+ "\n"
+ "\tClient-mode positional arguments are:\n\n"
+ "\t\tPATH-TO-DER-CSR PRETEND-CLIENT-PRINCIPAL [...]\n\n"
+ "\twhere {...} are requested features that must be granted\n"
+ "\tif the request is only partially authorized.\n\n"
+ "\tClient example:\n\t\t%s PKCS10:/tmp/csr.der foo@TEST.H5L.SE\n",
+ getprogname());
+ exit(e);
+ return e;
+}
+
+static const char *sysplugin_dirs[] = {
+#ifdef _WIN32
+ "$ORIGIN",
+#else
+ "$ORIGIN/../lib/plugin/kdc",
+#endif
+#ifdef __APPLE__
+ LIBDIR "/plugin/kdc",
+#endif
+ NULL
+};
+
+static void
+load_plugins(krb5_context context)
+{
+ const char * const *dirs = sysplugin_dirs;
+#ifndef _WIN32
+ char **cfdirs;
+
+ cfdirs = krb5_config_get_strings(context, NULL, "kdc", "plugin_dir", NULL);
+ if (cfdirs)
+ dirs = (const char * const *)cfdirs;
+#endif
+
+ _krb5_load_plugins(context, "kdc", (const char **)dirs);
+
+#ifndef _WIN32
+ krb5_config_free_strings(cfdirs);
+#endif
+}
+
+static char *string_encode(const char *);
+static int stat_authz(const char *, const char *);
+
+static krb5_error_code
+authorize(const char *subject, const char *thing)
+{
+ krb5_error_code ret;
+ char *s = NULL;
+
+ s = string_encode(subject);
+ if (s == NULL)
+ return ENOMEM;
+
+ ret = stat_authz(s, thing);
+ if (ret == ENOENT)
+ ret = stat_authz(s, "all");
+ if (ret == ENOENT)
+ ret = EACCES;
+ free(s);
+ return ret;
+}
+
+static void
+service(void *ctx,
+ const heim_octet_string *req,
+ const heim_icred cred,
+ heim_ipc_complete complete_cb,
+ heim_sipc_call complete_cb_data)
+{
+ krb5_error_code ret = 0;
+ struct rk_strpool *result = NULL;
+ krb5_data rep;
+ const char *subject;
+ char *cmd;
+ char *next = NULL;
+ char *res = NULL;
+ char *tok;
+ char *s;
+ int none_granted = 1;
+ int all_granted = 1;
+ int first = 1;
+
+ /*
+ * A krb5_context and log facility for logging would be nice, but this is
+ * all just for testing.
+ */
+
+ (void)ctx;
+
+ cmd = strndup(req->data, req->length);
+ if (cmd == NULL)
+ errx(1, "Out of memory");
+
+ if (strncmp(cmd, "check ", sizeof("check ") - 1) != 0) {
+ rep.data = "Invalid request command (must be \"check ...\")";
+ rep.length = sizeof("Invalid request command (must be \"check ...\")") - 1;
+ (*complete_cb)(complete_cb_data, EINVAL, &rep);
+ free(cmd);
+ return;
+ }
+
+ s = cmd + sizeof("check ") - 1;
+ subject = strtok_r(s, " ", &next);
+ s = NULL;
+
+ while ((tok = strtok_r(s, " ", &next))) {
+ int ret2;
+
+ ret2 = authorize(subject, tok);
+ result = rk_strpoolprintf(result, "%s%s:%s",
+ first ? "" : ",",
+ tok,
+ ret2 == 0 ? "granted" : "denied");
+ if (ret2 == 0)
+ none_granted = 0;
+ else
+ all_granted = 0;
+
+ if (ret2 != 0 && ret == 0)
+ ret = ret2;
+
+ first = 0;
+ }
+ free(cmd);
+
+ if (ret == 0 && all_granted) {
+ rk_strpoolfree(result);
+
+ rep.data = "granted";
+ rep.length = sizeof("granted") - 1;
+ (*complete_cb)(complete_cb_data, 0, &rep);
+ return;
+ }
+
+ if (none_granted && ignore_flag) {
+ rk_strpoolfree(result);
+
+ rep.data = "ignore";
+ rep.length = sizeof("ignore") - 1;
+ (*complete_cb)(complete_cb_data, KRB5_PLUGIN_NO_HANDLE, &rep);
+ return;
+ }
+
+ s = rk_strpoolcollect(result); /* frees `result' */
+ if (s == NULL) {
+ rep.data = "denied out-of-memory";
+ rep.length = sizeof("denied out-of-memory") - 1;
+ (*complete_cb)(complete_cb_data, KRB5_PLUGIN_NO_HANDLE, &rep);
+ return;
+ }
+
+ if (asprintf(&res, "denied %s", s) == -1)
+ errx(1, "Out of memory");
+ if (res == NULL)
+ errx(1, "Out of memory");
+
+ rep.data = res;
+ rep.length = strlen(res);
+
+ (*complete_cb)(complete_cb_data, ret, &rep);
+ free(res);
+ free(s);
+}
+
+static char *
+make_feature_argument(const char *kind,
+ hx509_san_type san_type,
+ const char *value)
+{
+ const char *san_type_str = NULL;
+ char *s = NULL;
+
+ if (strcmp(kind, "san") != 0) {
+ if (asprintf(&s, "%s=%s", kind, value) == -1 || s == NULL)
+ errx(1, "Out of memory");
+ return s;
+ }
+
+ switch (san_type) {
+ case HX509_SAN_TYPE_EMAIL:
+ san_type_str = "email";
+ break;
+ case HX509_SAN_TYPE_DNSNAME:
+ san_type_str = "dnsname";
+ break;
+ case HX509_SAN_TYPE_DN:
+ san_type_str = "dn";
+ break;
+ case HX509_SAN_TYPE_REGISTERED_ID:
+ san_type_str = "registered_id";
+ break;
+ case HX509_SAN_TYPE_XMPP:
+ san_type_str = "xmpp";
+ break;
+ case HX509_SAN_TYPE_PKINIT:
+ case HX509_SAN_TYPE_MS_UPN:
+ san_type_str = "pkinit";
+ break;
+ case HX509_SAN_TYPE_DNSSRV:
+ san_type_str = "dnssrv";
+ break;
+ default:
+ warnx("SAN type not supported");
+ return "";
+ }
+
+ if (asprintf(&s, "san_%s=%s", san_type_str, value) == -1 || s == NULL)
+ errx(1, "Out of memory");
+ return s;
+}
+
+int
+main(int argc, char **argv)
+{
+ krb5_log_facility *logf;
+ krb5_error_code ret;
+ krb5_context context;
+ hx509_request csr;
+ krb5_principal princ = NULL;
+ const char *argv0 = argv[0];
+ int optidx = 0;
+
+ setprogname(argv[0]);
+ if (getarg(args, num_args, argc, argv, &optidx))
+ return usage(1);
+ if (help_flag)
+ return usage(0);
+ if (version_flag) {
+ print_version(argv[0]);
+ return 0;
+ }
+
+ if ((errno = krb5_init_context(&context)))
+ err(1, "Could not initialize krb5_context");
+ if ((ret = krb5_initlog(context, argv0, &logf)) ||
+ (ret = krb5_addlog_dest(context, logf, "0-5/STDERR")))
+ krb5_err(context, 1, ret, "Could not set up logging to stderr");
+ load_plugins(context);
+
+ if (server_flag && daemon_flag)
+ daemon_child_flag = roken_detach_prep(argc, argv, "--daemon-child");
+
+ argc -= optidx;
+ argv += optidx;
+
+ if (socket_dir)
+ setenv("HEIM_IPC_DIR", socket_dir, 1);
+
+ if (server_flag) {
+ const char *svc;
+ heim_sipc un;
+
+ rk_pidfile(NULL);
+
+ svc = krb5_config_get_string(context, NULL,
+ app_string ? app_string : "kdc",
+ "ipc_csr_authorizer", "service", NULL);
+ if (svc == NULL)
+ svc = "org.h5l.csr_authorizer";
+
+ /* `service' is our request handler; `argv' is its callback data */
+ ret = heim_sipc_service_unix(svc, service, NULL, &un);
+ if (ret)
+ krb5_err(context, 1, ret,
+ "Could not setup service on Unix domain socket "
+ "%s/.heim_%s-socket", socket_dir, svc);
+
+ roken_detach_finish(NULL, daemon_child_flag);
+
+ /* Enter the IPC event loop */
+ heim_ipc_main();
+ return 0;
+ }
+
+ /* Client mode */
+ if (argc < 2)
+ usage(1);
+
+ /* Parse the given CSR */
+ if ((ret = hx509_request_parse(context->hx509ctx, argv[0], &csr)))
+ krb5_err(context, 1, ret, "Could not parse PKCS#10 CSR from %s", argv[0]);
+
+ /*
+ * Parse the client principal that we'll pretend is an authenticated client
+ * principal.
+ */
+ if ((ret = krb5_parse_name(context, argv[1], &princ)))
+ krb5_err(context, 1, ret, "Could not parse principal %s", argv[1]);
+
+ /* Call the authorizer */
+ ret = kdc_authorize_csr(context, app_string, csr, princ);
+
+ if (ret) {
+ unsigned n = hx509_request_count_unauthorized(csr);
+ size_t i, k;
+ int ret2 = 0;
+ int good = -1;
+
+ /*
+ * Check partial approval of SANs.
+ *
+ * Iterate over the SANs in the request, and for each check if a) it
+ * was granted, b) it's on the remainder of our argv[].
+ */
+ for (i = 0; ret2 == 0; i++) {
+ hx509_san_type san_type;
+ char *feature = NULL;
+ char *san = NULL;
+ int granted;
+
+ ret2 = hx509_request_get_san(csr, i, &san_type, &san);
+ if (ret2)
+ break;
+
+ feature = make_feature_argument("san", san_type, san);
+
+ granted = hx509_request_san_authorized_p(csr, i);
+ for (k = 2; k < argc; k++) {
+ if (strcmp(feature, argv[k]) != 0)
+ continue;
+
+ /* The SAN is on our command line */
+ if (granted && good == -1)
+ good = 1;
+ else if (!granted)
+ good = 0;
+ break;
+ }
+
+ hx509_xfree(san);
+ }
+
+ /* Check partial approval of EKUs */
+ for (i = 0; ret2 == 0; i++) {
+ char *feature = NULL;
+ char *eku = NULL;
+ int granted;
+
+ ret2 = hx509_request_get_eku(csr, i, &eku);
+ if (ret2)
+ break;
+
+ feature = make_feature_argument("eku", 0, eku);
+
+ granted = hx509_request_eku_authorized_p(csr, i);
+ for (k = 2; k < argc; k++) {
+ if (strcmp(feature, argv[k]) != 0)
+ continue;
+
+ /* The SAN is on our command line */
+ if (granted && good == -1)
+ good = 1;
+ else if (!granted)
+ good = 0;
+ break;
+ }
+
+ hx509_xfree(eku);
+ }
+
+ if (good != 1) {
+ krb5_free_principal(context, princ);
+ _krb5_unload_plugins(context, "kdc");
+ hx509_request_free(&csr);
+ krb5_err(context, 1, ret,
+ "Authorization failed with %u rejected features", n);
+ }
+ }
+
+ printf("Authorized!\n");
+ krb5_free_principal(context, princ);
+ _krb5_unload_plugins(context, "kdc");
+ krb5_free_context(context);
+ hx509_request_free(&csr);
+ return 0;
+}
+
+/*
+ * string_encode_sz() and string_encode() encode a string to be safe for use as
+ * a file name. They function very much like URL encoders, but '~' also gets
+ * encoded, and '@', '-', '_', and non-leading '.' do not.
+ *
+ * A corresponding decoder is not needed.
+ */
+static size_t
+string_encode_sz(const char *in)
+{
+ size_t sz = strlen(in);
+ int first = 1;
+
+ while (*in) {
+ char c = *(in++);
+
+ switch (c) {
+ case '@':
+ case '-':
+ case '_':
+ break;
+ case '.':
+ if (first)
+ sz += 2;
+ break;
+ default:
+ if (!isalnum((unsigned char)c))
+ sz += 2;
+ }
+ first = 0;
+ }
+ return sz;
+}
+
+static char *
+string_encode(const char *in)
+{
+ size_t len = strlen(in);
+ size_t sz = string_encode_sz(in);
+ size_t i, k;
+ char *s;
+ int first = 1;
+
+ if ((s = malloc(sz + 1)) == NULL)
+ return NULL;
+ s[sz] = '\0';
+
+ for (i = k = 0; i < len; i++, first = 0) {
+ unsigned char c = ((const unsigned char *)in)[i];
+
+ switch (c) {
+ case '@':
+ case '-':
+ case '_':
+ s[k++] = c;
+ break;
+ case '.':
+ if (first) {
+ s[k++] = '%';
+ s[k++] = "0123456789abcdef"[(c&0xff)>>4];
+ s[k++] = "0123456789abcdef"[(c&0x0f)];
+ } else {
+ s[k++] = c;
+ }
+ break;
+ default:
+ if (isalnum(c)) {
+ s[k++] = c;
+ } else {
+ s[k++] = '%';
+ s[k++] = "0123456789abcdef"[(c&0xff)>>4];
+ s[k++] = "0123456789abcdef"[(c&0x0f)];
+ }
+ }
+ }
+ return s;
+}
+
+static int
+stat_authz(const char *subject,
+ const char *thing)
+{
+ struct stat st;
+ char *p = NULL;
+ int ret;
+
+ if (authz_dir == NULL)
+ return KRB5_PLUGIN_NO_HANDLE;
+ if (thing)
+ ret = asprintf(&p, "%s/%s/%s", authz_dir, subject, thing);
+ else
+ ret = asprintf(&p, "%s/%s", authz_dir, subject);
+ if (ret == -1 || p == NULL)
+ return ENOMEM;
+ ret = stat(p, &st);
+ free(p);
+ return ret == 0 ? 0 : errno;
+}
diff --git a/third_party/heimdal/kdc/test_kdc_ca.c b/third_party/heimdal/kdc/test_kdc_ca.c
new file mode 100644
index 0000000..4d80c96
--- /dev/null
+++ b/third_party/heimdal/kdc/test_kdc_ca.c
@@ -0,0 +1,178 @@
+#include "kdc_locl.h"
+
+static int authorized_flag;
+static int help_flag;
+static char *lifetime_string;
+static const char *app_string = "kdc";
+static int version_flag;
+
+struct getargs args[] = {
+ { "authorized", 'A', arg_flag, &authorized_flag,
+ "Assume CSR is authorized", NULL },
+ { "lifetime", 'l', arg_string, &lifetime_string,
+ "Certificate lifetime desired", "TIME" },
+ { "help", 'h', arg_flag, &help_flag,
+ "Print usage message", NULL },
+ { "app", 'a', arg_string, &app_string,
+ "Application name (kdc or bx509); default: kdc", "APPNAME" },
+ { "version", 'v', arg_flag, &version_flag,
+ "Print version", NULL }
+};
+size_t num_args = sizeof(args) / sizeof(args[0]);
+
+static int
+usage(int e)
+{
+ arg_printusage(args, num_args, NULL,
+ "PRINC PKCS10:/path/to/der/CSR [HX509-STORE]");
+ fprintf(stderr,
+ "\n\tTest kx509/bx509 online CA issuer functionality.\n"
+ "\n\tIf --authorized / -A not given, then authorizer plugins\n"
+ "\twill be invoked.\n"
+ "\n\tUse --app kdc to test the kx509 configuration.\n"
+ "\tUse --app bx509 to test the bx509 configuration.\n\n\t"
+ "Example: %s foo@TEST.H5L.SE PKCS10:/tmp/csr PEM-FILE:/tmp/cert\n",
+ getprogname());
+ exit(e);
+ return e;
+}
+
+static const char *sysplugin_dirs[] = {
+#ifdef _WIN32
+ "$ORIGIN",
+#else
+ "$ORIGIN/../lib/plugin/kdc",
+#endif
+#ifdef __APPLE__
+ LIBDIR "/plugin/kdc",
+#endif
+ NULL
+};
+
+static void
+load_plugins(krb5_context context)
+{
+ const char * const *dirs = sysplugin_dirs;
+#ifndef _WIN32
+ char **cfdirs;
+
+ cfdirs = krb5_config_get_strings(context, NULL, "kdc", "plugin_dir", NULL);
+ if (cfdirs)
+ dirs = (const char * const *)cfdirs;
+#endif
+
+ _krb5_load_plugins(context, "kdc", (const char **)dirs);
+
+#ifndef _WIN32
+ krb5_config_free_strings(cfdirs);
+#endif
+}
+
+int
+main(int argc, char **argv)
+{
+ krb5_log_facility *logf = NULL;
+ krb5_error_code ret;
+ krb5_principal p = NULL;
+ krb5_context context;
+ krb5_times t;
+ hx509_request req = NULL;
+ hx509_certs store = NULL;
+ hx509_certs certs = NULL;
+ const char *argv0 = argv[0];
+ const char *out = "MEMORY:junk-it";
+ time_t req_life = 0;
+ int optidx = 0;
+
+ setprogname(argv[0]);
+ if (getarg(args, num_args, argc, argv, &optidx))
+ return usage(1);
+ if (help_flag)
+ return usage(0);
+ if (version_flag) {
+ print_version(argv[0]);
+ return 0;
+ }
+
+ argc -= optidx;
+ argv += optidx;
+
+ if (argc < 3 || argc > 4)
+ usage(1);
+
+ if ((errno = krb5_init_context(&context)))
+ err(1, "Could not initialize krb5_context");
+ if ((ret = krb5_initlog(context, argv0, &logf)) ||
+ (ret = krb5_addlog_dest(context, logf, "0-5/STDERR")))
+ krb5_err(context, 1, ret, "Could not set up logging to stderr");
+ load_plugins(context);
+ if ((ret = krb5_parse_name(context, argv[0], &p)))
+ krb5_err(context, 1, ret, "Could not parse principal %s", argv[0]);
+ if ((ret = hx509_request_parse(context->hx509ctx, argv[1], &req)))
+ krb5_err(context, 1, ret, "Could not parse PKCS#10 CSR from %s", argv[1]);
+
+ if (authorized_flag) {
+ KeyUsage ku = int2KeyUsage(0);
+ size_t i;
+ char *s;
+
+ /* Mark all the things authorized */
+ ku.digitalSignature = 1;
+ hx509_request_authorize_ku(req, ku);
+
+ for (i = 0; ret == 0; i++) {
+ ret = hx509_request_get_eku(req, i, &s);
+ free(s); s = NULL;
+ if (ret == 0)
+ hx509_request_authorize_eku(req, i);
+ }
+ if (ret == HX509_NO_ITEM)
+ ret = 0;
+
+ for (i = 0; ret == 0; i++) {
+ hx509_san_type san_type;
+
+ ret = hx509_request_get_san(req, i, &san_type, &s);
+ free(s); s = NULL;
+ if (ret == 0)
+ hx509_request_authorize_san(req, i);
+ }
+ if (ret && ret != HX509_NO_ITEM)
+ krb5_err(context, 1, ret,
+ "Failed to mark requested extensions authorized");
+ } else if ((ret = kdc_authorize_csr(context, app_string, req, p))) {
+ krb5_err(context, 1, ret,
+ "Requested certificate extensions rejected by policy");
+ }
+
+ memset(&t, 0, sizeof(t));
+ t.starttime = time(NULL);
+ t.endtime = t.starttime + 3600;
+ req_life = lifetime_string ? parse_time(lifetime_string, "day") : 0;
+ if ((ret = kdc_issue_certificate(context, app_string, logf, req, p, &t,
+ req_life, 1, &certs)))
+ krb5_err(context, 1, ret, "Certificate issuance failed");
+
+ if (argv[2])
+ out = argv[2];
+
+ if ((ret = hx509_certs_init(context->hx509ctx, out, HX509_CERTS_CREATE,
+ NULL, &store)) ||
+ (ret = hx509_certs_merge(context->hx509ctx, store, certs)) ||
+ (ret = hx509_certs_store(context->hx509ctx, store, 0, NULL)))
+ /*
+ * If the store is a MEMORY store, say, we're really not being asked to
+ * store -- we're just testing the online CA functionality without
+ * wanting to inspect the result.
+ */
+ if (ret != HX509_UNSUPPORTED_OPERATION)
+ krb5_err(context, 1, ret,
+ "Could not store certificate and chain in %s", out);
+ _krb5_unload_plugins(context, "kdc");
+ krb5_free_principal(context, p);
+ krb5_free_context(context);
+ hx509_request_free(&req);
+ hx509_certs_free(&store);
+ hx509_certs_free(&certs);
+ return 0;
+}
diff --git a/third_party/heimdal/kdc/test_token_validator.c b/third_party/heimdal/kdc/test_token_validator.c
new file mode 100644
index 0000000..2e4e9dc
--- /dev/null
+++ b/third_party/heimdal/kdc/test_token_validator.c
@@ -0,0 +1,121 @@
+#include "kdc_locl.h"
+
+static int help_flag;
+static int version_flag;
+static char *realm;
+static char *app;
+static struct getarg_strings audiences;
+
+struct getargs args[] = {
+ { "app", 'A', arg_string, &app,
+ "app name (krb5.conf section)", "APP-NAME" },
+ { "help", 'h', arg_flag, &help_flag,
+ "Print usage message", NULL },
+ { NULL, 'r', arg_string, &realm,
+ "Realm name for plugin configuration", "REALM" },
+ { NULL, 'a', arg_strings, &audiences,
+ "expected token acceptor audience (hostname)", "ACCEPTOR-HOSTNAME" },
+ { "version", 'v', arg_flag, &version_flag, "Print version", NULL }
+};
+size_t num_args = sizeof(args) / sizeof(args[0]);
+
+static int
+usage(int e)
+{
+ arg_printusage(args, num_args, NULL, "TOKEN-TYPE TOKEN");
+ exit(e);
+ return e;
+}
+
+static const char *sysplugin_dirs[] = {
+#ifdef _WIN32
+ "$ORIGIN",
+#else
+ "$ORIGIN/../lib/plugin/kdc",
+#endif
+#ifdef __APPLE__
+ LIBDIR "/plugin/kdc",
+#endif
+ NULL
+};
+
+static void
+load_plugins(krb5_context context)
+{
+ const char * const *dirs = sysplugin_dirs;
+#ifndef _WIN32
+ char **cfdirs;
+
+ cfdirs = krb5_config_get_strings(context, NULL, "kdc", "plugin_dir", NULL);
+ if (cfdirs)
+ dirs = (const char * const *)cfdirs;
+#endif
+
+ _krb5_load_plugins(context, "kdc", (const char **)dirs);
+
+#ifndef _WIN32
+ krb5_config_free_strings(cfdirs);
+#endif
+}
+
+int
+main(int argc, char **argv)
+{
+ krb5_error_code ret;
+ krb5_context context;
+ krb5_data token;
+ const char *token_type;
+ krb5_principal actual_princ = NULL;
+ krb5_times token_times;
+ size_t bufsz = 0;
+ char *buf = NULL;
+ char *s = NULL;
+ int optidx = 0;
+
+ setprogname(argv[0]);
+ if (getarg(args, num_args, argc, argv, &optidx))
+ return usage(1);
+ if (help_flag)
+ return usage(0);
+ if (version_flag) {
+ print_version(argv[0]);
+ return 0;
+ }
+
+ argc -= optidx;
+ argv += optidx;
+
+ if (argc != 2)
+ usage(1);
+
+ if (krb5_init_context(&context))
+ err(1, "Could not initialize krb5_context");
+
+ load_plugins(context);
+
+ token_type = argv[0];
+ token.data = argv[1];
+ if (strcmp(token.data, "-") == 0) {
+ if (getline(&buf, &bufsz, stdin) < 0)
+ err(1, "Could not read token from stdin");
+ token.length = bufsz;
+ token.data = buf;
+ } else {
+ token.length = strlen(token.data);
+ }
+ if ((ret = kdc_validate_token(context, realm, token_type, &token,
+ (const char * const *)audiences.strings,
+ audiences.num_strings, &actual_princ,
+ &token_times)))
+ krb5_err(context, 1, ret, "Could not validate %s token", token_type);
+ if (actual_princ && (ret = krb5_unparse_name(context, actual_princ, &s)))
+ krb5_err(context, 1, ret, "Could not display principal name");
+ if (s)
+ printf("Token is valid. Actual principal: %s\n", s);
+ else
+ printf("Token is valid.");
+ _krb5_unload_plugins(context, "kdc");
+ krb5_free_principal(context, actual_princ);
+ krb5_free_context(context);
+ return 0;
+}
diff --git a/third_party/heimdal/kdc/token_validator.c b/third_party/heimdal/kdc/token_validator.c
new file mode 100644
index 0000000..858fdfa
--- /dev/null
+++ b/third_party/heimdal/kdc/token_validator.c
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2019 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "kdc_locl.h"
+#include "token_validator_plugin.h"
+
+struct plctx {
+ const char *realm;
+ const char *token_kind;
+ krb5_data *token;
+ const char * const *audiences;
+ size_t naudiences;
+ krb5_boolean result;
+ krb5_principal actual_principal;
+ krb5_times token_times;
+};
+
+static krb5_error_code KRB5_LIB_CALL
+plcallback(krb5_context context, const void *plug, void *plugctx, void *userctx)
+{
+ const krb5plugin_token_validator_ftable *validator = plug;
+ krb5_error_code ret;
+ struct plctx *plctx = userctx;
+
+ ret = validator->validate(plugctx, context, plctx->realm,
+ plctx->token_kind, plctx->token,
+ plctx->audiences, plctx->naudiences,
+ &plctx->result, &plctx->actual_principal,
+ &plctx->token_times);
+ if (ret) {
+ krb5_free_principal(context, plctx->actual_principal);
+ plctx->actual_principal = NULL;
+ }
+ return ret;
+}
+
+static const char *plugin_deps[] = { "krb5", NULL };
+
+static struct heim_plugin_data token_validator_data = {
+ "kdc",
+ KDC_PLUGIN_BEARER,
+ 1,
+ plugin_deps,
+ krb5_get_instance
+};
+
+/*
+ * Invoke a plugin to validate a JWT/SAML/OIDC token and partially-evaluate
+ * access control.
+ */
+KDC_LIB_FUNCTION krb5_error_code KDC_LIB_CALL
+kdc_validate_token(krb5_context context,
+ const char *realm,
+ const char *token_kind,
+ krb5_data *token,
+ const char * const *audiences,
+ size_t naudiences,
+ krb5_principal *actual_principal,
+ krb5_times *token_times)
+{
+ krb5_error_code ret;
+ struct plctx ctx;
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.realm = realm;
+ ctx.token_kind = token_kind;
+ ctx.token = token;
+ ctx.audiences = audiences;
+ ctx.naudiences = naudiences;
+ ctx.result = FALSE;
+ ctx.actual_principal = NULL;
+
+ krb5_clear_error_message(context);
+ ret = _krb5_plugin_run_f(context, &token_validator_data, 0, &ctx,
+ plcallback);
+ if (ret == 0 && ctx.result && actual_principal) {
+ *actual_principal = ctx.actual_principal;
+ ctx.actual_principal = NULL;
+ }
+
+ if (token_times)
+ *token_times = ctx.token_times;
+
+ krb5_free_principal(context, ctx.actual_principal);
+ if (ret)
+ krb5_prepend_error_message(context, ret, "bearer token validation "
+ "failed: ");
+ else if (!ctx.result)
+ krb5_set_error_message(context, ret = EACCES,
+ "bearer token validation failed");
+ return ret;
+}
diff --git a/third_party/heimdal/kdc/token_validator_plugin.h b/third_party/heimdal/kdc/token_validator_plugin.h
new file mode 100644
index 0000000..8ff0014
--- /dev/null
+++ b/third_party/heimdal/kdc/token_validator_plugin.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2019 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef HEIMDAL_KDC_BEARER_TOKEN_PLUGIN_H
+#define HEIMDAL_KDC_BEARER_TOKEN_PLUGIN_H 1
+
+#define KDC_PLUGIN_BEARER "kdc_token_validator"
+#define KDC_PLUGIN_BEARER_VERSION_0 0
+
+/*
+ * @param init Plugin initialization function (see krb5-plugin(7))
+ * @param minor_version The plugin minor version number (0)
+ * @param fini Plugin finalization function
+ * @param validate Plugin token validation function
+ *
+ * The validate field is the plugin entry point that performs the bearer token
+ * validation operation however the plugin desires. It is invoked in no
+ * particular order relative to other bearer token validator plugins. The
+ * plugin validate function must return KRB5_PLUGIN_NO_HANDLE if the rule is
+ * not applicable to it.
+ *
+ * The plugin validate function has the following arguments, in this
+ * order:
+ *
+ * -# plug_ctx, the context value output by the plugin's init function
+ * -# context, a krb5_context
+ * -# realm, a const char *
+ * -# token_type, a const char *
+ * -# token, a krb5_data *
+ * -# audiences, a const pointer to an array of const char * containing
+ * expected audiences of the token (aka, acceptor names)
+ * -# naudiences, a size_t count of audiences
+ * -# requested_principal, a krb5_const_principal
+ * -# validation result, a pointer to a krb5_boolean
+ * -# actual principal, a krb5_principal * output parameter (optional)
+ *
+ * @ingroup krb5_support
+ */
+typedef struct krb5plugin_token_validator_ftable_desc {
+ HEIM_PLUGIN_FTABLE_COMMON_ELEMENTS(krb5_context);
+ krb5_error_code (KRB5_LIB_CALL *validate)(void *, /*plug_ctx*/
+ krb5_context,
+ const char *, /*realm*/
+ const char *, /*token_type*/
+ krb5_data *, /*token*/
+ const char * const *, /*audiences*/
+ size_t, /*naudiences*/
+ krb5_boolean *, /*valid*/
+ krb5_principal *, /*actual_principal*/
+ krb5_times *); /*token_times*/
+} krb5plugin_token_validator_ftable;
+
+#endif /* HEIMDAL_KDC_BEARER_TOKEN_PLUGIN_H */
diff --git a/third_party/heimdal/kdc/version-script.map b/third_party/heimdal/kdc/version-script.map
new file mode 100644
index 0000000..c644b30
--- /dev/null
+++ b/third_party/heimdal/kdc/version-script.map
@@ -0,0 +1,113 @@
+# $Id$
+
+HEIMDAL_KDC_1.0 {
+ global:
+ kdc_authorize_csr;
+ kdc_get_instance;
+ kdc_issue_certificate;
+ kdc_log;
+ kdc_log_msg;
+ kdc_log_msg_va;
+ kdc_openlog;
+ kdc_check_flags;
+ kdc_validate_token;
+ krb5_kdc_plugin_init;
+ krb5_kdc_get_config;
+ krb5_kdc_get_time;
+ krb5_kdc_pkinit_config;
+ krb5_kdc_set_dbinfo;
+ krb5_kdc_process_krb5_request;
+ krb5_kdc_process_request;
+ krb5_kdc_save_request;
+ krb5_kdc_update_time;
+ krb5_kdc_pk_initialize;
+ kdc_request_set_attribute;
+ kdc_request_get_attribute;
+ kdc_request_copy_attribute;
+ kdc_request_delete_attribute;
+ kdc_request_add_encrypted_padata;
+ kdc_request_add_pac_buffer;
+ kdc_request_add_reply_padata;
+ kdc_request_get_addr;
+ kdc_request_get_armor_client;
+ kdc_request_get_armor_clientdb;
+ kdc_request_get_armor_pac;
+ kdc_request_get_armor_server;
+ kdc_request_get_canon_client_princ;
+ kdc_request_get_client;
+ kdc_request_get_clientdb;
+ kdc_request_get_client_princ;
+ kdc_request_get_context;
+ kdc_request_get_config;
+ kdc_request_get_cname;
+ kdc_request_get_error_code;
+ kdc_request_get_explicit_armor_pac;
+ kdc_request_get_explicit_armor_clientdb;
+ kdc_request_get_explicit_armor_client;
+ kdc_request_get_explicit_armor_present;
+ kdc_request_get_explicit_armor_server;
+ kdc_request_get_from;
+ kdc_request_get_krbtgt;
+ kdc_request_get_krbtgtdb;
+ kdc_request_get_krbtgt_princ;
+ kdc_request_get_pac;
+ kdc_request_get_pac_attributes;
+ kdc_request_get_rep;
+ kdc_request_get_reply_key;
+ kdc_request_get_req;
+ kdc_request_get_request;
+ kdc_request_get_server;
+ kdc_request_get_serverdb;
+ kdc_request_get_server_princ;
+ kdc_request_get_sname;
+ kdc_request_get_ticket;
+ kdc_request_get_tv_end;
+ kdc_request_get_tv_start;
+ kdc_request_set_canon_client_princ;
+ kdc_request_set_client_princ;
+ kdc_request_set_cname;
+ kdc_request_set_e_data;
+ kdc_request_set_error_code;
+ kdc_request_set_krbtgt_princ;
+ kdc_request_set_pac;
+ kdc_request_set_pac_attributes;
+ kdc_request_set_rep;
+ kdc_request_set_reply_key;
+ kdc_request_set_server_princ;
+ kdc_request_set_sname;
+ kdc_audit_addkv;
+ kdc_audit_addkv_number;
+ kdc_audit_addkv_object;
+ kdc_audit_addkv_timediff;
+ kdc_audit_addaddrs;
+ kdc_audit_addreason;
+ kdc_audit_getkv;
+ kdc_audit_setkv_bool;
+ kdc_audit_setkv_number;
+ kdc_audit_setkv_object;
+ kdc_audit_vaddkv;
+ kdc_audit_vaddreason;
+ _kdc_audit_trail;
+
+ kdc_object_alloc;
+ kdc_object_retain;
+ kdc_object_release;
+ kdc_bool_create;
+ kdc_bool_get_value;
+ kdc_array_iterate;
+ kdc_array_get_length;
+ kdc_array_get_value;
+ kdc_array_copy_value;
+ kdc_string_create;
+ kdc_string_get_utf8;
+ kdc_data_create;
+ kdc_data_get_data;
+ kdc_number_create;
+ kdc_number_get_value;
+
+ # needed for digest-service
+ _kdc_db_fetch;
+ _kdc_free_ent;
+ local:
+ *;
+};