2 * Copyright (C) 2004-2011 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.26.12.2 2011-04-07 23:05:01 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.
72 #include ISC_PLATFORM_KRB5HEADER
74 static unsigned char krb5_mech_oid_bytes[] = {
75 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02
78 #ifndef USE_ISC_SPNEGO
79 static unsigned char spnego_mech_oid_bytes[] = {
80 0x2b, 0x06, 0x01, 0x05, 0x05, 0x02
84 static gss_OID_desc mech_oid_set_array[] = {
85 { sizeof(krb5_mech_oid_bytes), krb5_mech_oid_bytes },
86 #ifndef USE_ISC_SPNEGO
87 { sizeof(spnego_mech_oid_bytes), spnego_mech_oid_bytes },
91 static gss_OID_set_desc mech_oid_set = {
92 sizeof(mech_oid_set_array) / sizeof(*mech_oid_set_array),
98 #define REGION_TO_GBUFFER(r, gb) \
100 (gb).length = (r).length; \
101 (gb).value = (r).base; \
104 #define GBUFFER_TO_REGION(gb, r) \
106 (r).length = (gb).length; \
107 (r).base = (gb).value; \
111 #define RETERR(x) do { \
113 if (result != ISC_R_SUCCESS) \
119 name_to_gbuffer(dns_name_t *name, isc_buffer_t *buffer,
120 gss_buffer_desc *gbuffer)
122 dns_name_t tname, *namep;
126 if (!dns_name_isabsolute(name))
131 dns_name_init(&tname, NULL);
132 labels = dns_name_countlabels(name);
133 dns_name_getlabelsequence(name, 0, labels - 1, &tname);
137 result = dns_name_toprincipal(namep, buffer);
138 isc_buffer_putuint8(buffer, 0);
139 isc_buffer_usedregion(buffer, &r);
140 REGION_TO_GBUFFER(r, *gbuffer);
144 log_cred(const gss_cred_id_t cred) {
145 OM_uint32 gret, minor, lifetime;
147 gss_buffer_desc gbuffer;
148 gss_cred_usage_t usage;
149 const char *usage_text;
152 gret = gss_inquire_cred(&minor, cred, &gname, &lifetime, &usage, NULL);
153 if (gret != GSS_S_COMPLETE) {
154 gss_log(3, "failed gss_inquire_cred: %s",
155 gss_error_tostring(gret, minor, buf, sizeof(buf)));
159 gret = gss_display_name(&minor, gname, &gbuffer, NULL);
160 if (gret != GSS_S_COMPLETE)
161 gss_log(3, "failed gss_display_name: %s",
162 gss_error_tostring(gret, minor, buf, sizeof(buf)));
166 usage_text = "GSS_C_BOTH";
169 usage_text = "GSS_C_INITIATE";
172 usage_text = "GSS_C_ACCEPT";
177 gss_log(3, "gss cred: \"%s\", %s, %lu", (char *)gbuffer.value,
178 usage_text, (unsigned long)lifetime);
181 if (gret == GSS_S_COMPLETE) {
182 if (gbuffer.length != 0U) {
183 gret = gss_release_buffer(&minor, &gbuffer);
184 if (gret != GSS_S_COMPLETE)
185 gss_log(3, "failed gss_release_buffer: %s",
186 gss_error_tostring(gret, minor, buf,
191 gret = gss_release_name(&minor, &gname);
192 if (gret != GSS_S_COMPLETE)
193 gss_log(3, "failed gss_release_name: %s",
194 gss_error_tostring(gret, minor, buf, sizeof(buf)));
200 * check for the most common configuration errors.
202 * The errors checked for are:
203 * - tkey-gssapi-credential doesn't start with DNS/
204 * - the default realm in /etc/krb5.conf and the
205 * tkey-gssapi-credential bind config option don't match
207 * Note that if tkey-gssapi-keytab is set then these configure checks
208 * are not performed, and runtime errors from gssapi are used instead
211 check_config(const char *gss_name) {
213 krb5_context krb5_ctx;
214 char *krb5_realm = NULL;
216 if (strncasecmp(gss_name, "DNS/", 4) != 0) {
217 gss_log(ISC_LOG_ERROR, "tkey-gssapi-credential (%s) "
218 "should start with 'DNS/'", gss_name);
222 if (krb5_init_context(&krb5_ctx) != 0) {
223 gss_log(ISC_LOG_ERROR, "Unable to initialise krb5 context");
226 if (krb5_get_default_realm(krb5_ctx, &krb5_realm) != 0) {
227 gss_log(ISC_LOG_ERROR, "Unable to get krb5 default realm");
228 krb5_free_context(krb5_ctx);
231 p = strchr(gss_name, '/');
233 gss_log(ISC_LOG_ERROR, "badly formatted "
234 "tkey-gssapi-credentials (%s)", gss_name);
235 krb5_free_context(krb5_ctx);
238 if (strcasecmp(p + 1, krb5_realm) != 0) {
239 gss_log(ISC_LOG_ERROR, "default realm from krb5.conf (%s) "
240 "does not match tkey-gssapi-credential (%s)",
241 krb5_realm, gss_name);
242 krb5_free_context(krb5_ctx);
245 krb5_free_context(krb5_ctx);
250 dst_gssapi_acquirecred(dns_name_t *name, isc_boolean_t initiate,
254 isc_buffer_t namebuf;
256 gss_buffer_desc gnamebuf;
257 unsigned char array[DNS_NAME_MAXTEXT + 1];
258 OM_uint32 gret, minor;
261 gss_cred_usage_t usage;
264 REQUIRE(cred != NULL && *cred == NULL);
267 * XXXSRA In theory we could use GSS_C_NT_HOSTBASED_SERVICE
268 * here when we're in the acceptor role, which would let us
269 * default the hostname and use a compiled in default service
270 * name of "DNS", giving one less thing to configure in
271 * named.conf. Unfortunately, this creates a circular
272 * dependency due to DNS-based realm lookup in at least one
273 * GSSAPI implementation (Heimdal). Oh well.
276 isc_buffer_init(&namebuf, array, sizeof(array));
277 name_to_gbuffer(name, &namebuf, &gnamebuf);
278 gret = gss_import_name(&minor, &gnamebuf,
279 GSS_C_NO_OID, &gname);
280 if (gret != GSS_S_COMPLETE) {
281 check_config((char *)array);
283 gss_log(3, "failed gss_import_name: %s",
284 gss_error_tostring(gret, minor, buf,
286 return (ISC_R_FAILURE);
291 /* Get the credentials. */
293 gss_log(3, "acquiring credentials for %s",
294 (char *)gnamebuf.value);
296 /* XXXDCL does this even make any sense? */
297 gss_log(3, "acquiring credentials for ?");
301 usage = GSS_C_INITIATE;
303 usage = GSS_C_ACCEPT;
305 gret = gss_acquire_cred(&minor, gname, GSS_C_INDEFINITE,
307 usage, cred, &mechs, &lifetime);
309 if (gret != GSS_S_COMPLETE) {
310 gss_log(3, "failed to acquire %s credentials for %s: %s",
311 initiate ? "initiate" : "accept",
312 (char *)gnamebuf.value,
313 gss_error_tostring(gret, minor, buf, sizeof(buf)));
314 check_config((char *)array);
315 return (ISC_R_FAILURE);
318 gss_log(4, "acquired %s credentials for %s",
319 initiate ? "initiate" : "accept",
320 (char *)gnamebuf.value);
324 return (ISC_R_SUCCESS);
330 return (ISC_R_NOTIMPLEMENTED);
335 dst_gssapi_identitymatchesrealmkrb5(dns_name_t *signer, dns_name_t *name,
339 char sbuf[DNS_NAME_FORMATSIZE];
340 char nbuf[DNS_NAME_FORMATSIZE];
341 char rbuf[DNS_NAME_FORMATSIZE];
347 * It is far, far easier to write the names we are looking at into
348 * a string, and do string operations on them.
350 isc_buffer_init(&buffer, sbuf, sizeof(sbuf));
351 dns_name_toprincipal(signer, &buffer);
352 isc_buffer_putuint8(&buffer, 0);
354 dns_name_format(name, nbuf, sizeof(nbuf));
355 dns_name_format(realm, rbuf, sizeof(rbuf));
358 * Find the realm portion. This is the part after the @. If it
359 * does not exist, we don't have something we like, so we fail our
362 rname = strchr(sbuf, '@');
364 return (isc_boolean_false);
369 * Find the host portion of the signer's name. We do this by
370 * searching for the first / character. We then check to make
371 * certain the instance name is "host"
374 * host/example.com@EXAMPLE.COM
376 sname = strchr(sbuf, '/');
378 return (isc_boolean_false);
381 if (strcmp(sbuf, "host") != 0)
382 return (isc_boolean_false);
385 * Now, we do a simple comparison between the name and the realm.
388 if ((strcasecmp(sname, nbuf) == 0)
389 && (strcmp(rname, rbuf) == 0))
390 return (isc_boolean_true);
392 if (strcmp(rname, rbuf) == 0)
393 return (isc_boolean_true);
396 return (isc_boolean_false);
401 return (isc_boolean_false);
406 dst_gssapi_identitymatchesrealmms(dns_name_t *signer, dns_name_t *name,
410 char sbuf[DNS_NAME_FORMATSIZE];
411 char nbuf[DNS_NAME_FORMATSIZE];
412 char rbuf[DNS_NAME_FORMATSIZE];
419 * It is far, far easier to write the names we are looking at into
420 * a string, and do string operations on them.
422 isc_buffer_init(&buffer, sbuf, sizeof(sbuf));
423 dns_name_toprincipal(signer, &buffer);
424 isc_buffer_putuint8(&buffer, 0);
426 dns_name_format(name, nbuf, sizeof(nbuf));
427 dns_name_format(realm, rbuf, sizeof(rbuf));
430 * Find the realm portion. This is the part after the @. If it
431 * does not exist, we don't have something we like, so we fail our
434 rname = strchr(sbuf, '@');
436 return (isc_boolean_false);
437 sname = strchr(sbuf, '$');
439 return (isc_boolean_false);
442 * Verify that the $ and @ follow one another.
444 if (rname - sname != 1)
445 return (isc_boolean_false);
448 * Find the host portion of the signer's name. Zero out the $ so
449 * it terminates the signer's name, and skip past the @ for
452 * All service principals in Microsoft format seem to be in
453 * machinename$@EXAMPLE.COM
461 * Find the first . in the target name, and make it the end of
462 * the string. The rest of the name has to match the realm.
465 nname = strchr(nbuf, '.');
467 return (isc_boolean_false);
472 * Now, we do a simple comparison between the name and the realm.
475 if ((strcasecmp(sname, nbuf) == 0)
476 && (strcmp(rname, rbuf) == 0)
477 && (strcasecmp(nname, rbuf) == 0))
478 return (isc_boolean_true);
480 if (strcmp(rname, rbuf) == 0)
481 return (isc_boolean_true);
485 return (isc_boolean_false);
490 return (isc_boolean_false);
495 dst_gssapi_releasecred(gss_cred_id_t *cred) {
497 OM_uint32 gret, minor;
500 REQUIRE(cred != NULL && *cred != NULL);
502 gret = gss_release_cred(&minor, cred);
503 if (gret != GSS_S_COMPLETE) {
504 /* Log the error, but still free the credential's memory */
505 gss_log(3, "failed releasing credential: %s",
506 gss_error_tostring(gret, minor, buf, sizeof(buf)));
510 return(ISC_R_SUCCESS);
514 return (ISC_R_NOTIMPLEMENTED);
520 * Format a gssapi error message info into a char ** on the given memory
521 * context. This is used to return gssapi error messages back up the
522 * call chain for reporting to the user.
525 gss_err_message(isc_mem_t *mctx, isc_uint32_t major, isc_uint32_t minor,
531 if (err_message == NULL || mctx == NULL) {
532 /* the caller doesn't want any error messages */
536 estr = gss_error_tostring(major, minor, buf, sizeof(buf));
538 (*err_message) = isc_mem_strdup(mctx, estr);
543 dst_gssapi_initctx(dns_name_t *name, isc_buffer_t *intoken,
544 isc_buffer_t *outtoken, gss_ctx_id_t *gssctx,
545 isc_mem_t *mctx, char **err_message)
549 isc_buffer_t namebuf;
551 OM_uint32 gret, minor, ret_flags, flags;
552 gss_buffer_desc gintoken, *gintokenp, gouttoken = GSS_C_EMPTY_BUFFER;
554 gss_buffer_desc gnamebuf;
555 unsigned char array[DNS_NAME_MAXTEXT + 1];
557 /* Client must pass us a valid gss_ctx_id_t here */
558 REQUIRE(gssctx != NULL);
559 REQUIRE(mctx != NULL);
561 isc_buffer_init(&namebuf, array, sizeof(array));
562 name_to_gbuffer(name, &namebuf, &gnamebuf);
564 /* Get the name as a GSS name */
565 gret = gss_import_name(&minor, &gnamebuf, GSS_C_NO_OID, &gname);
566 if (gret != GSS_S_COMPLETE) {
567 gss_err_message(mctx, gret, minor, err_message);
568 result = ISC_R_FAILURE;
572 if (intoken != NULL) {
573 /* Don't call gss_release_buffer for gintoken! */
574 REGION_TO_GBUFFER(*intoken, gintoken);
575 gintokenp = &gintoken;
581 * Note that we don't set GSS_C_SEQUENCE_FLAG as Windows DNS
582 * servers don't like it.
584 flags = GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG;
586 gret = gss_init_sec_context(&minor, GSS_C_NO_CREDENTIAL, gssctx,
587 gname, GSS_SPNEGO_MECHANISM, flags,
589 NULL, &gouttoken, &ret_flags, NULL);
591 if (gret != GSS_S_COMPLETE && gret != GSS_S_CONTINUE_NEEDED) {
592 gss_err_message(mctx, gret, minor, err_message);
593 gss_log(3, "Failure initiating security context: %s",
595 result = ISC_R_FAILURE;
600 * XXXSRA Not handled yet: RFC 3645 3.1.1: check ret_flags
601 * MUTUAL and INTEG flags, fail if either not set.
605 * RFC 2744 states the a valid output token has a non-zero length.
607 if (gouttoken.length != 0U) {
608 GBUFFER_TO_REGION(gouttoken, r);
609 RETERR(isc_buffer_copyregion(outtoken, &r));
610 (void)gss_release_buffer(&minor, &gouttoken);
612 (void)gss_release_name(&minor, &gname);
614 if (gret == GSS_S_COMPLETE)
615 result = ISC_R_SUCCESS;
617 result = DNS_R_CONTINUE;
629 return (ISC_R_NOTIMPLEMENTED);
634 dst_gssapi_acceptctx(gss_cred_id_t cred,
635 const char *gssapi_keytab,
636 isc_region_t *intoken, isc_buffer_t **outtoken,
637 gss_ctx_id_t *ctxout, dns_name_t *principal,
642 isc_buffer_t namebuf;
643 gss_buffer_desc gnamebuf = GSS_C_EMPTY_BUFFER, gintoken,
644 gouttoken = GSS_C_EMPTY_BUFFER;
645 OM_uint32 gret, minor;
646 gss_ctx_id_t context = GSS_C_NO_CONTEXT;
647 gss_name_t gname = NULL;
651 REQUIRE(outtoken != NULL && *outtoken == NULL);
653 REGION_TO_GBUFFER(*intoken, gintoken);
656 context = GSS_C_NO_CONTEXT;
660 if (gssapi_keytab != NULL) {
661 #ifdef ISC_PLATFORM_GSSAPI_KRB5_HEADER
662 gret = gsskrb5_register_acceptor_identity(gssapi_keytab);
663 if (gret != GSS_S_COMPLETE) {
665 "gsskrb5_register_acceptor_identity(%s): %s",
667 gss_error_tostring(gret, minor,
669 return (DNS_R_INVALIDTKEY);
673 * Minimize memory leakage by only setting KRB5_KTNAME
674 * if it needs to change.
676 const char *old = getenv("KRB5_KTNAME");
677 if (old == NULL || strcmp(old, gssapi_keytab) != 0) {
678 char *kt = malloc(strlen(gssapi_keytab) + 13);
680 return (ISC_R_NOMEMORY);
681 sprintf(kt, "KRB5_KTNAME=%s", gssapi_keytab);
683 return (ISC_R_NOMEMORY);
690 gret = gss_accept_sec_context(&minor, &context, cred, &gintoken,
691 GSS_C_NO_CHANNEL_BINDINGS, &gname,
692 NULL, &gouttoken, NULL, NULL, NULL);
694 result = ISC_R_FAILURE;
698 result = ISC_R_SUCCESS;
700 case GSS_S_CONTINUE_NEEDED:
701 result = DNS_R_CONTINUE;
703 case GSS_S_DEFECTIVE_TOKEN:
704 case GSS_S_DEFECTIVE_CREDENTIAL:
706 case GSS_S_DUPLICATE_TOKEN:
707 case GSS_S_OLD_TOKEN:
709 case GSS_S_CREDENTIALS_EXPIRED:
710 case GSS_S_BAD_BINDINGS:
711 case GSS_S_NO_CONTEXT:
714 result = DNS_R_INVALIDTKEY;
717 gss_log(3, "failed gss_accept_sec_context: %s",
718 gss_error_tostring(gret, minor, buf, sizeof(buf)));
722 if (gouttoken.length > 0U) {
723 RETERR(isc_buffer_allocate(mctx, outtoken, gouttoken.length));
724 GBUFFER_TO_REGION(gouttoken, r);
725 RETERR(isc_buffer_copyregion(*outtoken, &r));
726 (void)gss_release_buffer(&minor, &gouttoken);
729 if (gret == GSS_S_COMPLETE) {
730 gret = gss_display_name(&minor, gname, &gnamebuf, NULL);
731 if (gret != GSS_S_COMPLETE) {
732 gss_log(3, "failed gss_display_name: %s",
733 gss_error_tostring(gret, minor,
735 RETERR(ISC_R_FAILURE);
739 * Compensate for a bug in Solaris8's implementation
740 * of gss_display_name(). Should be harmless in any
741 * case, since principal names really should not
742 * contain null characters.
744 if (gnamebuf.length > 0U &&
745 ((char *)gnamebuf.value)[gnamebuf.length - 1] == '\0')
748 gss_log(3, "gss-api source name (accept) is %.*s",
749 (int)gnamebuf.length, (char *)gnamebuf.value);
751 GBUFFER_TO_REGION(gnamebuf, r);
752 isc_buffer_init(&namebuf, r.base, r.length);
753 isc_buffer_add(&namebuf, r.length);
755 RETERR(dns_name_fromtext(principal, &namebuf, dns_rootname,
758 if (gnamebuf.length != 0U) {
759 gret = gss_release_buffer(&minor, &gnamebuf);
760 if (gret != GSS_S_COMPLETE)
761 gss_log(3, "failed gss_release_buffer: %s",
762 gss_error_tostring(gret, minor, buf,
771 gret = gss_release_name(&minor, &gname);
772 if (gret != GSS_S_COMPLETE)
773 gss_log(3, "failed gss_release_name: %s",
774 gss_error_tostring(gret, minor, buf,
781 UNUSED(gssapi_keytab);
788 return (ISC_R_NOTIMPLEMENTED);
793 dst_gssapi_deletectx(isc_mem_t *mctx, gss_ctx_id_t *gssctx)
796 OM_uint32 gret, minor;
801 REQUIRE(gssctx != NULL && *gssctx != NULL);
803 /* Delete the context from the GSS provider */
804 gret = gss_delete_sec_context(&minor, gssctx, GSS_C_NO_BUFFER);
805 if (gret != GSS_S_COMPLETE) {
806 /* Log the error, but still free the context's memory */
807 gss_log(3, "Failure deleting security context %s",
808 gss_error_tostring(gret, minor, buf, sizeof(buf)));
810 return(ISC_R_SUCCESS);
814 return (ISC_R_NOTIMPLEMENTED);
819 gss_error_tostring(isc_uint32_t major, isc_uint32_t minor,
820 char *buf, size_t buflen) {
822 gss_buffer_desc msg_minor = GSS_C_EMPTY_BUFFER,
823 msg_major = GSS_C_EMPTY_BUFFER;
824 OM_uint32 msg_ctx, minor_stat;
826 /* Handle major status */
828 (void)gss_display_status(&minor_stat, major, GSS_C_GSS_CODE,
829 GSS_C_NULL_OID, &msg_ctx, &msg_major);
831 /* Handle minor status */
833 (void)gss_display_status(&minor_stat, minor, GSS_C_MECH_CODE,
834 GSS_C_NULL_OID, &msg_ctx, &msg_minor);
836 snprintf(buf, buflen, "GSSAPI error: Major = %s, Minor = %s.",
837 (char *)msg_major.value, (char *)msg_minor.value);
839 if (msg_major.length != 0U)
840 (void)gss_release_buffer(&minor_stat, &msg_major);
841 if (msg_minor.length != 0U)
842 (void)gss_release_buffer(&minor_stat, &msg_minor);
845 snprintf(buf, buflen, "GSSAPI error: Major = %u, Minor = %u.",
853 gss_log(int level, const char *fmt, ...) {
857 isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_GENERAL,
858 DNS_LOGMODULE_TKEY, ISC_LOG_DEBUG(level), fmt, ap);