2 * Copyright (C) 2004-2013 Internet Systems Consortium, Inc. ("ISC")
3 * Copyright (C) 2000, 2001 Internet Software Consortium.
5 * Permission to use, copy, modify, and/or distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
10 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11 * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
12 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15 * PERFORMANCE OF THIS SOFTWARE.
18 /* $Id: gssapictx.c,v 1.29 2011/08/29 06:33:25 marka Exp $ */
26 #include <isc/buffer.h>
28 #include <isc/entropy.h>
33 #include <isc/print.h>
34 #include <isc/platform.h>
35 #include <isc/random.h>
36 #include <isc/string.h>
40 #include <dns/fixedname.h>
42 #include <dns/rdata.h>
43 #include <dns/rdataclass.h>
44 #include <dns/result.h>
45 #include <dns/types.h>
46 #include <dns/keyvalues.h>
49 #include <dst/gssapi.h>
50 #include <dst/result.h>
52 #include "dst_internal.h"
55 * If we're using our own SPNEGO implementation (see configure.in),
56 * pull it in now. Otherwise, we just use whatever GSSAPI supplies.
58 #if defined(GSSAPI) && defined(USE_ISC_SPNEGO)
60 #define gss_accept_sec_context gss_accept_sec_context_spnego
61 #define gss_init_sec_context gss_init_sec_context_spnego
65 * Solaris8 apparently needs an explicit OID set, and Solaris10 needs
66 * one for anything but Kerberos. Supplying an explicit OID set
67 * doesn't appear to hurt anything in other implementations, so we
68 * always use one. If we're not using our own SPNEGO implementation,
69 * we include SPNEGO's OID.
73 #include <krb5/krb5.h>
75 #include ISC_PLATFORM_KRB5HEADER
78 static unsigned char krb5_mech_oid_bytes[] = {
79 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02
82 #ifndef USE_ISC_SPNEGO
83 static unsigned char spnego_mech_oid_bytes[] = {
84 0x2b, 0x06, 0x01, 0x05, 0x05, 0x02
88 static gss_OID_desc mech_oid_set_array[] = {
89 { sizeof(krb5_mech_oid_bytes), krb5_mech_oid_bytes },
90 #ifndef USE_ISC_SPNEGO
91 { sizeof(spnego_mech_oid_bytes), spnego_mech_oid_bytes },
95 static gss_OID_set_desc mech_oid_set = {
96 sizeof(mech_oid_set_array) / sizeof(*mech_oid_set_array),
102 #define REGION_TO_GBUFFER(r, gb) \
104 (gb).length = (r).length; \
105 (gb).value = (r).base; \
108 #define GBUFFER_TO_REGION(gb, r) \
110 (r).length = (unsigned int)(gb).length; \
111 (r).base = (gb).value; \
115 #define RETERR(x) do { \
117 if (result != ISC_R_SUCCESS) \
123 name_to_gbuffer(dns_name_t *name, isc_buffer_t *buffer,
124 gss_buffer_desc *gbuffer)
126 dns_name_t tname, *namep;
130 if (!dns_name_isabsolute(name))
135 dns_name_init(&tname, NULL);
136 labels = dns_name_countlabels(name);
137 dns_name_getlabelsequence(name, 0, labels - 1, &tname);
141 result = dns_name_toprincipal(namep, buffer);
142 RUNTIME_CHECK(result == ISC_R_SUCCESS);
143 isc_buffer_putuint8(buffer, 0);
144 isc_buffer_usedregion(buffer, &r);
145 REGION_TO_GBUFFER(r, *gbuffer);
149 log_cred(const gss_cred_id_t cred) {
150 OM_uint32 gret, minor, lifetime;
152 gss_buffer_desc gbuffer;
153 gss_cred_usage_t usage;
154 const char *usage_text;
157 gret = gss_inquire_cred(&minor, cred, &gname, &lifetime, &usage, NULL);
158 if (gret != GSS_S_COMPLETE) {
159 gss_log(3, "failed gss_inquire_cred: %s",
160 gss_error_tostring(gret, minor, buf, sizeof(buf)));
164 gret = gss_display_name(&minor, gname, &gbuffer, NULL);
165 if (gret != GSS_S_COMPLETE)
166 gss_log(3, "failed gss_display_name: %s",
167 gss_error_tostring(gret, minor, buf, sizeof(buf)));
171 usage_text = "GSS_C_BOTH";
174 usage_text = "GSS_C_INITIATE";
177 usage_text = "GSS_C_ACCEPT";
182 gss_log(3, "gss cred: \"%s\", %s, %lu", (char *)gbuffer.value,
183 usage_text, (unsigned long)lifetime);
186 if (gret == GSS_S_COMPLETE) {
187 if (gbuffer.length != 0U) {
188 gret = gss_release_buffer(&minor, &gbuffer);
189 if (gret != GSS_S_COMPLETE)
190 gss_log(3, "failed gss_release_buffer: %s",
191 gss_error_tostring(gret, minor, buf,
196 gret = gss_release_name(&minor, &gname);
197 if (gret != GSS_S_COMPLETE)
198 gss_log(3, "failed gss_release_name: %s",
199 gss_error_tostring(gret, minor, buf, sizeof(buf)));
205 * check for the most common configuration errors.
207 * The errors checked for are:
208 * - tkey-gssapi-credential doesn't start with DNS/
209 * - the default realm in /etc/krb5.conf and the
210 * tkey-gssapi-credential bind config option don't match
212 * Note that if tkey-gssapi-keytab is set then these configure checks
213 * are not performed, and runtime errors from gssapi are used instead
216 check_config(const char *gss_name) {
218 krb5_context krb5_ctx;
219 char *krb5_realm = NULL;
221 if (strncasecmp(gss_name, "DNS/", 4) != 0) {
222 gss_log(ISC_LOG_ERROR, "tkey-gssapi-credential (%s) "
223 "should start with 'DNS/'", gss_name);
227 if (krb5_init_context(&krb5_ctx) != 0) {
228 gss_log(ISC_LOG_ERROR, "Unable to initialise krb5 context");
231 if (krb5_get_default_realm(krb5_ctx, &krb5_realm) != 0) {
232 gss_log(ISC_LOG_ERROR, "Unable to get krb5 default realm");
233 krb5_free_context(krb5_ctx);
236 p = strchr(gss_name, '/');
238 gss_log(ISC_LOG_ERROR, "badly formatted "
239 "tkey-gssapi-credentials (%s)", gss_name);
240 krb5_free_context(krb5_ctx);
243 if (strcasecmp(p + 1, krb5_realm) != 0) {
244 gss_log(ISC_LOG_ERROR, "default realm from krb5.conf (%s) "
245 "does not match tkey-gssapi-credential (%s)",
246 krb5_realm, gss_name);
247 krb5_free_context(krb5_ctx);
250 krb5_free_context(krb5_ctx);
255 dst_gssapi_acquirecred(dns_name_t *name, isc_boolean_t initiate,
260 isc_buffer_t namebuf;
262 gss_buffer_desc gnamebuf;
263 unsigned char array[DNS_NAME_MAXTEXT + 1];
264 OM_uint32 gret, minor;
266 gss_cred_usage_t usage;
269 REQUIRE(cred != NULL && *cred == NULL);
272 * XXXSRA In theory we could use GSS_C_NT_HOSTBASED_SERVICE
273 * here when we're in the acceptor role, which would let us
274 * default the hostname and use a compiled in default service
275 * name of "DNS", giving one less thing to configure in
276 * named.conf. Unfortunately, this creates a circular
277 * dependency due to DNS-based realm lookup in at least one
278 * GSSAPI implementation (Heimdal). Oh well.
281 isc_buffer_init(&namebuf, array, sizeof(array));
282 name_to_gbuffer(name, &namebuf, &gnamebuf);
283 gret = gss_import_name(&minor, &gnamebuf,
284 GSS_C_NO_OID, &gname);
285 if (gret != GSS_S_COMPLETE) {
286 check_config((char *)array);
288 gss_log(3, "failed gss_import_name: %s",
289 gss_error_tostring(gret, minor, buf,
291 return (ISC_R_FAILURE);
296 /* Get the credentials. */
298 gss_log(3, "acquiring credentials for %s",
299 (char *)gnamebuf.value);
301 /* XXXDCL does this even make any sense? */
302 gss_log(3, "acquiring credentials for ?");
306 usage = GSS_C_INITIATE;
308 usage = GSS_C_ACCEPT;
310 gret = gss_acquire_cred(&minor, gname, GSS_C_INDEFINITE,
311 &mech_oid_set, usage, cred, NULL, &lifetime);
313 if (gret != GSS_S_COMPLETE) {
314 gss_log(3, "failed to acquire %s credentials for %s: %s",
315 initiate ? "initiate" : "accept",
316 (gname != NULL) ? (char *)gnamebuf.value : "?",
317 gss_error_tostring(gret, minor, buf, sizeof(buf)));
319 check_config((char *)array);
320 result = ISC_R_FAILURE;
324 gss_log(4, "acquired %s credentials for %s",
325 initiate ? "initiate" : "accept",
326 (gname != NULL) ? (char *)gnamebuf.value : "?");
329 result = ISC_R_SUCCESS;
333 gret = gss_release_name(&minor, &gname);
334 if (gret != GSS_S_COMPLETE)
335 gss_log(3, "failed gss_release_name: %s",
336 gss_error_tostring(gret, minor, buf,
342 REQUIRE(cred != NULL && *cred == NULL);
348 return (ISC_R_NOTIMPLEMENTED);
353 dst_gssapi_identitymatchesrealmkrb5(dns_name_t *signer, dns_name_t *name,
357 char sbuf[DNS_NAME_FORMATSIZE];
358 char nbuf[DNS_NAME_FORMATSIZE];
359 char rbuf[DNS_NAME_FORMATSIZE];
366 * It is far, far easier to write the names we are looking at into
367 * a string, and do string operations on them.
369 isc_buffer_init(&buffer, sbuf, sizeof(sbuf));
370 result = dns_name_toprincipal(signer, &buffer);
371 RUNTIME_CHECK(result == ISC_R_SUCCESS);
372 isc_buffer_putuint8(&buffer, 0);
374 dns_name_format(name, nbuf, sizeof(nbuf));
375 dns_name_format(realm, rbuf, sizeof(rbuf));
378 * Find the realm portion. This is the part after the @. If it
379 * does not exist, we don't have something we like, so we fail our
382 rname = strchr(sbuf, '@');
384 return (isc_boolean_false);
389 * Find the host portion of the signer's name. We do this by
390 * searching for the first / character. We then check to make
391 * certain the instance name is "host"
394 * host/example.com@EXAMPLE.COM
396 sname = strchr(sbuf, '/');
398 return (isc_boolean_false);
401 if (strcmp(sbuf, "host") != 0)
402 return (isc_boolean_false);
405 * Now, we do a simple comparison between the name and the realm.
408 if ((strcasecmp(sname, nbuf) == 0)
409 && (strcmp(rname, rbuf) == 0))
410 return (isc_boolean_true);
412 if (strcmp(rname, rbuf) == 0)
413 return (isc_boolean_true);
416 return (isc_boolean_false);
421 return (isc_boolean_false);
426 dst_gssapi_identitymatchesrealmms(dns_name_t *signer, dns_name_t *name,
430 char sbuf[DNS_NAME_FORMATSIZE];
431 char nbuf[DNS_NAME_FORMATSIZE];
432 char rbuf[DNS_NAME_FORMATSIZE];
440 * It is far, far easier to write the names we are looking at into
441 * a string, and do string operations on them.
443 isc_buffer_init(&buffer, sbuf, sizeof(sbuf));
444 result = dns_name_toprincipal(signer, &buffer);
445 RUNTIME_CHECK(result == ISC_R_SUCCESS);
446 isc_buffer_putuint8(&buffer, 0);
448 dns_name_format(name, nbuf, sizeof(nbuf));
449 dns_name_format(realm, rbuf, sizeof(rbuf));
452 * Find the realm portion. This is the part after the @. If it
453 * does not exist, we don't have something we like, so we fail our
456 rname = strchr(sbuf, '@');
458 return (isc_boolean_false);
459 sname = strchr(sbuf, '$');
461 return (isc_boolean_false);
464 * Verify that the $ and @ follow one another.
466 if (rname - sname != 1)
467 return (isc_boolean_false);
470 * Find the host portion of the signer's name. Zero out the $ so
471 * it terminates the signer's name, and skip past the @ for
474 * All service principals in Microsoft format seem to be in
475 * machinename$@EXAMPLE.COM
483 * Find the first . in the target name, and make it the end of
484 * the string. The rest of the name has to match the realm.
487 nname = strchr(nbuf, '.');
489 return (isc_boolean_false);
494 * Now, we do a simple comparison between the name and the realm.
497 if ((strcasecmp(sname, nbuf) == 0)
498 && (strcmp(rname, rbuf) == 0)
499 && (strcasecmp(nname, rbuf) == 0))
500 return (isc_boolean_true);
502 if (strcmp(rname, rbuf) == 0)
503 return (isc_boolean_true);
507 return (isc_boolean_false);
512 return (isc_boolean_false);
517 dst_gssapi_releasecred(gss_cred_id_t *cred) {
519 OM_uint32 gret, minor;
522 REQUIRE(cred != NULL && *cred != NULL);
524 gret = gss_release_cred(&minor, cred);
525 if (gret != GSS_S_COMPLETE) {
526 /* Log the error, but still free the credential's memory */
527 gss_log(3, "failed releasing credential: %s",
528 gss_error_tostring(gret, minor, buf, sizeof(buf)));
532 return(ISC_R_SUCCESS);
536 return (ISC_R_NOTIMPLEMENTED);
542 * Format a gssapi error message info into a char ** on the given memory
543 * context. This is used to return gssapi error messages back up the
544 * call chain for reporting to the user.
547 gss_err_message(isc_mem_t *mctx, isc_uint32_t major, isc_uint32_t minor,
553 if (err_message == NULL || mctx == NULL) {
554 /* the caller doesn't want any error messages */
558 estr = gss_error_tostring(major, minor, buf, sizeof(buf));
560 (*err_message) = isc_mem_strdup(mctx, estr);
565 dst_gssapi_initctx(dns_name_t *name, isc_buffer_t *intoken,
566 isc_buffer_t *outtoken, gss_ctx_id_t *gssctx,
567 isc_mem_t *mctx, char **err_message)
571 isc_buffer_t namebuf;
573 OM_uint32 gret, minor, ret_flags, flags;
574 gss_buffer_desc gintoken, *gintokenp, gouttoken = GSS_C_EMPTY_BUFFER;
576 gss_buffer_desc gnamebuf;
577 unsigned char array[DNS_NAME_MAXTEXT + 1];
579 /* Client must pass us a valid gss_ctx_id_t here */
580 REQUIRE(gssctx != NULL);
581 REQUIRE(mctx != NULL);
583 isc_buffer_init(&namebuf, array, sizeof(array));
584 name_to_gbuffer(name, &namebuf, &gnamebuf);
586 /* Get the name as a GSS name */
587 gret = gss_import_name(&minor, &gnamebuf, GSS_C_NO_OID, &gname);
588 if (gret != GSS_S_COMPLETE) {
589 gss_err_message(mctx, gret, minor, err_message);
590 result = ISC_R_FAILURE;
594 if (intoken != NULL) {
595 /* Don't call gss_release_buffer for gintoken! */
596 REGION_TO_GBUFFER(*intoken, gintoken);
597 gintokenp = &gintoken;
603 * Note that we don't set GSS_C_SEQUENCE_FLAG as Windows DNS
604 * servers don't like it.
606 flags = GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG;
608 gret = gss_init_sec_context(&minor, GSS_C_NO_CREDENTIAL, gssctx,
609 gname, GSS_SPNEGO_MECHANISM, flags,
611 NULL, &gouttoken, &ret_flags, NULL);
613 if (gret != GSS_S_COMPLETE && gret != GSS_S_CONTINUE_NEEDED) {
614 gss_err_message(mctx, gret, minor, err_message);
615 if (err_message != NULL && *err_message != NULL)
616 gss_log(3, "Failure initiating security context: %s",
619 gss_log(3, "Failure initiating security context");
621 result = ISC_R_FAILURE;
626 * XXXSRA Not handled yet: RFC 3645 3.1.1: check ret_flags
627 * MUTUAL and INTEG flags, fail if either not set.
631 * RFC 2744 states the a valid output token has a non-zero length.
633 if (gouttoken.length != 0U) {
634 GBUFFER_TO_REGION(gouttoken, r);
635 RETERR(isc_buffer_copyregion(outtoken, &r));
636 (void)gss_release_buffer(&minor, &gouttoken);
639 if (gret == GSS_S_COMPLETE)
640 result = ISC_R_SUCCESS;
642 result = DNS_R_CONTINUE;
645 (void)gss_release_name(&minor, &gname);
655 return (ISC_R_NOTIMPLEMENTED);
660 dst_gssapi_acceptctx(gss_cred_id_t cred,
661 const char *gssapi_keytab,
662 isc_region_t *intoken, isc_buffer_t **outtoken,
663 gss_ctx_id_t *ctxout, dns_name_t *principal,
668 isc_buffer_t namebuf;
669 gss_buffer_desc gnamebuf = GSS_C_EMPTY_BUFFER, gintoken,
670 gouttoken = GSS_C_EMPTY_BUFFER;
671 OM_uint32 gret, minor;
672 gss_ctx_id_t context = GSS_C_NO_CONTEXT;
673 gss_name_t gname = NULL;
677 REQUIRE(outtoken != NULL && *outtoken == NULL);
679 REGION_TO_GBUFFER(*intoken, gintoken);
682 context = GSS_C_NO_CONTEXT;
686 if (gssapi_keytab != NULL) {
687 #if defined(ISC_PLATFORM_GSSAPI_KRB5_HEADER) || defined(WIN32)
688 gret = gsskrb5_register_acceptor_identity(gssapi_keytab);
689 if (gret != GSS_S_COMPLETE) {
691 "gsskrb5_register_acceptor_identity(%s): %s",
693 gss_error_tostring(gret, 0, buf, sizeof(buf)));
694 return (DNS_R_INVALIDTKEY);
698 * Minimize memory leakage by only setting KRB5_KTNAME
699 * if it needs to change.
701 const char *old = getenv("KRB5_KTNAME");
702 if (old == NULL || strcmp(old, gssapi_keytab) != 0) {
703 char *kt = malloc(strlen(gssapi_keytab) + 13);
705 return (ISC_R_NOMEMORY);
706 sprintf(kt, "KRB5_KTNAME=%s", gssapi_keytab);
708 return (ISC_R_NOMEMORY);
715 gret = gss_accept_sec_context(&minor, &context, cred, &gintoken,
716 GSS_C_NO_CHANNEL_BINDINGS, &gname,
717 NULL, &gouttoken, NULL, NULL, NULL);
719 result = ISC_R_FAILURE;
723 result = ISC_R_SUCCESS;
725 case GSS_S_CONTINUE_NEEDED:
726 result = DNS_R_CONTINUE;
728 case GSS_S_DEFECTIVE_TOKEN:
729 case GSS_S_DEFECTIVE_CREDENTIAL:
731 case GSS_S_DUPLICATE_TOKEN:
732 case GSS_S_OLD_TOKEN:
734 case GSS_S_CREDENTIALS_EXPIRED:
735 case GSS_S_BAD_BINDINGS:
736 case GSS_S_NO_CONTEXT:
739 result = DNS_R_INVALIDTKEY;
742 gss_log(3, "failed gss_accept_sec_context: %s",
743 gss_error_tostring(gret, minor, buf, sizeof(buf)));
747 if (gouttoken.length > 0U) {
748 RETERR(isc_buffer_allocate(mctx, outtoken,
749 (unsigned int)gouttoken.length));
750 GBUFFER_TO_REGION(gouttoken, r);
751 RETERR(isc_buffer_copyregion(*outtoken, &r));
752 (void)gss_release_buffer(&minor, &gouttoken);
755 if (gret == GSS_S_COMPLETE) {
756 gret = gss_display_name(&minor, gname, &gnamebuf, NULL);
757 if (gret != GSS_S_COMPLETE) {
758 gss_log(3, "failed gss_display_name: %s",
759 gss_error_tostring(gret, minor,
761 RETERR(ISC_R_FAILURE);
765 * Compensate for a bug in Solaris8's implementation
766 * of gss_display_name(). Should be harmless in any
767 * case, since principal names really should not
768 * contain null characters.
770 if (gnamebuf.length > 0U &&
771 ((char *)gnamebuf.value)[gnamebuf.length - 1] == '\0')
774 gss_log(3, "gss-api source name (accept) is %.*s",
775 (int)gnamebuf.length, (char *)gnamebuf.value);
777 GBUFFER_TO_REGION(gnamebuf, r);
778 isc_buffer_init(&namebuf, r.base, r.length);
779 isc_buffer_add(&namebuf, r.length);
781 RETERR(dns_name_fromtext(principal, &namebuf, dns_rootname,
784 if (gnamebuf.length != 0U) {
785 gret = gss_release_buffer(&minor, &gnamebuf);
786 if (gret != GSS_S_COMPLETE)
787 gss_log(3, "failed gss_release_buffer: %s",
788 gss_error_tostring(gret, minor, buf,
797 gret = gss_release_name(&minor, &gname);
798 if (gret != GSS_S_COMPLETE)
799 gss_log(3, "failed gss_release_name: %s",
800 gss_error_tostring(gret, minor, buf,
807 UNUSED(gssapi_keytab);
814 return (ISC_R_NOTIMPLEMENTED);
819 dst_gssapi_deletectx(isc_mem_t *mctx, gss_ctx_id_t *gssctx)
822 OM_uint32 gret, minor;
827 REQUIRE(gssctx != NULL && *gssctx != NULL);
829 /* Delete the context from the GSS provider */
830 gret = gss_delete_sec_context(&minor, gssctx, GSS_C_NO_BUFFER);
831 if (gret != GSS_S_COMPLETE) {
832 /* Log the error, but still free the context's memory */
833 gss_log(3, "Failure deleting security context %s",
834 gss_error_tostring(gret, minor, buf, sizeof(buf)));
836 return(ISC_R_SUCCESS);
840 return (ISC_R_NOTIMPLEMENTED);
845 gss_error_tostring(isc_uint32_t major, isc_uint32_t minor,
846 char *buf, size_t buflen) {
848 gss_buffer_desc msg_minor = GSS_C_EMPTY_BUFFER,
849 msg_major = GSS_C_EMPTY_BUFFER;
850 OM_uint32 msg_ctx, minor_stat;
852 /* Handle major status */
854 (void)gss_display_status(&minor_stat, major, GSS_C_GSS_CODE,
855 GSS_C_NULL_OID, &msg_ctx, &msg_major);
857 /* Handle minor status */
859 (void)gss_display_status(&minor_stat, minor, GSS_C_MECH_CODE,
860 GSS_C_NULL_OID, &msg_ctx, &msg_minor);
862 snprintf(buf, buflen, "GSSAPI error: Major = %s, Minor = %s.",
863 (char *)msg_major.value, (char *)msg_minor.value);
865 if (msg_major.length != 0U)
866 (void)gss_release_buffer(&minor_stat, &msg_major);
867 if (msg_minor.length != 0U)
868 (void)gss_release_buffer(&minor_stat, &msg_minor);
871 snprintf(buf, buflen, "GSSAPI error: Major = %u, Minor = %u.",
879 gss_log(int level, const char *fmt, ...) {
883 isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_GENERAL,
884 DNS_LOGMODULE_TKEY, ISC_LOG_DEBUG(level), fmt, ap);