2 * Copyright (C) 2004-2010 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.14.104.4 2010/07/09 05:14:08 each Exp $ */
25 #include <isc/buffer.h>
27 #include <isc/entropy.h>
31 #include <isc/print.h>
32 #include <isc/platform.h>
33 #include <isc/random.h>
34 #include <isc/string.h>
38 #include <dns/fixedname.h>
40 #include <dns/rdata.h>
41 #include <dns/rdataclass.h>
42 #include <dns/result.h>
43 #include <dns/types.h>
44 #include <dns/keyvalues.h>
47 #include <dst/gssapi.h>
48 #include <dst/result.h>
50 #include "dst_internal.h"
53 * If we're using our own SPNEGO implementation (see configure.in),
54 * pull it in now. Otherwise, we just use whatever GSSAPI supplies.
56 #if defined(GSSAPI) && defined(USE_ISC_SPNEGO)
58 #define gss_accept_sec_context gss_accept_sec_context_spnego
59 #define gss_init_sec_context gss_init_sec_context_spnego
63 * Solaris8 apparently needs an explicit OID set, and Solaris10 needs
64 * one for anything but Kerberos. Supplying an explicit OID set
65 * doesn't appear to hurt anything in other implementations, so we
66 * always use one. If we're not using our own SPNEGO implementation,
67 * we include SPNEGO's OID.
70 #include ISC_PLATFORM_KRB5HEADER
72 static unsigned char krb5_mech_oid_bytes[] = {
73 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02
76 #ifndef USE_ISC_SPNEGO
77 static unsigned char spnego_mech_oid_bytes[] = {
78 0x2b, 0x06, 0x01, 0x05, 0x05, 0x02
82 static gss_OID_desc mech_oid_set_array[] = {
83 { sizeof(krb5_mech_oid_bytes), krb5_mech_oid_bytes },
84 #ifndef USE_ISC_SPNEGO
85 { sizeof(spnego_mech_oid_bytes), spnego_mech_oid_bytes },
89 static gss_OID_set_desc mech_oid_set = {
90 sizeof(mech_oid_set_array) / sizeof(*mech_oid_set_array),
96 #define REGION_TO_GBUFFER(r, gb) \
98 (gb).length = (r).length; \
99 (gb).value = (r).base; \
102 #define GBUFFER_TO_REGION(gb, r) \
104 (r).length = (gb).length; \
105 (r).base = (gb).value; \
109 #define RETERR(x) do { \
111 if (result != ISC_R_SUCCESS) \
117 name_to_gbuffer(dns_name_t *name, isc_buffer_t *buffer,
118 gss_buffer_desc *gbuffer)
120 dns_name_t tname, *namep;
124 if (!dns_name_isabsolute(name))
129 dns_name_init(&tname, NULL);
130 labels = dns_name_countlabels(name);
131 dns_name_getlabelsequence(name, 0, labels - 1, &tname);
135 result = dns_name_toprincipal(namep, buffer);
136 isc_buffer_putuint8(buffer, 0);
137 isc_buffer_usedregion(buffer, &r);
138 REGION_TO_GBUFFER(r, *gbuffer);
142 log_cred(const gss_cred_id_t cred) {
143 OM_uint32 gret, minor, lifetime;
145 gss_buffer_desc gbuffer;
146 gss_cred_usage_t usage;
147 const char *usage_text;
150 gret = gss_inquire_cred(&minor, cred, &gname, &lifetime, &usage, NULL);
151 if (gret != GSS_S_COMPLETE) {
152 gss_log(3, "failed gss_inquire_cred: %s",
153 gss_error_tostring(gret, minor, buf, sizeof(buf)));
157 gret = gss_display_name(&minor, gname, &gbuffer, NULL);
158 if (gret != GSS_S_COMPLETE)
159 gss_log(3, "failed gss_display_name: %s",
160 gss_error_tostring(gret, minor, buf, sizeof(buf)));
164 usage_text = "GSS_C_BOTH";
167 usage_text = "GSS_C_INITIATE";
170 usage_text = "GSS_C_ACCEPT";
175 gss_log(3, "gss cred: \"%s\", %s, %lu", (char *)gbuffer.value,
176 usage_text, (unsigned long)lifetime);
179 if (gret == GSS_S_COMPLETE) {
180 if (gbuffer.length != 0) {
181 gret = gss_release_buffer(&minor, &gbuffer);
182 if (gret != GSS_S_COMPLETE)
183 gss_log(3, "failed gss_release_buffer: %s",
184 gss_error_tostring(gret, minor, buf,
189 gret = gss_release_name(&minor, &gname);
190 if (gret != GSS_S_COMPLETE)
191 gss_log(3, "failed gss_release_name: %s",
192 gss_error_tostring(gret, minor, buf, sizeof(buf)));
198 * check for the most common configuration errors.
200 * The errors checked for are:
201 * - tkey-gssapi-credential doesn't start with DNS/
202 * - the default realm in /etc/krb5.conf and the
203 * tkey-gssapi-credential bind config option don't match
206 dst_gssapi_check_config(const char *gss_name) {
208 krb5_context krb5_ctx;
209 char *krb5_realm = NULL;
211 if (strncasecmp(gss_name, "DNS/", 4) != 0) {
212 gss_log(ISC_LOG_ERROR, "tkey-gssapi-credential (%s) "
213 "should start with 'DNS/'", gss_name);
217 if (krb5_init_context(&krb5_ctx) != 0) {
218 gss_log(ISC_LOG_ERROR, "Unable to initialise krb5 context");
221 if (krb5_get_default_realm(krb5_ctx, &krb5_realm) != 0) {
222 gss_log(ISC_LOG_ERROR, "Unable to get krb5 default realm");
223 krb5_free_context(krb5_ctx);
226 p = strchr(gss_name, '/');
228 gss_log(ISC_LOG_ERROR, "badly formatted "
229 "tkey-gssapi-credentials (%s)", gss_name);
230 krb5_free_context(krb5_ctx);
233 if (strcasecmp(p + 1, krb5_realm) != 0) {
234 gss_log(ISC_LOG_ERROR, "default realm from krb5.conf (%s) "
235 "does not match tkey-gssapi-credential (%s)",
236 krb5_realm, gss_name);
237 krb5_free_context(krb5_ctx);
240 krb5_free_context(krb5_ctx);
245 dst_gssapi_acquirecred(dns_name_t *name, isc_boolean_t initiate,
249 isc_buffer_t namebuf;
251 gss_buffer_desc gnamebuf;
252 unsigned char array[DNS_NAME_MAXTEXT + 1];
253 OM_uint32 gret, minor;
256 gss_cred_usage_t usage;
259 REQUIRE(cred != NULL && *cred == NULL);
262 * XXXSRA In theory we could use GSS_C_NT_HOSTBASED_SERVICE
263 * here when we're in the acceptor role, which would let us
264 * default the hostname and use a compiled in default service
265 * name of "DNS", giving one less thing to configure in
266 * named.conf. Unfortunately, this creates a circular
267 * dependency due to DNS-based realm lookup in at least one
268 * GSSAPI implementation (Heimdal). Oh well.
271 isc_buffer_init(&namebuf, array, sizeof(array));
272 name_to_gbuffer(name, &namebuf, &gnamebuf);
273 gret = gss_import_name(&minor, &gnamebuf,
274 GSS_C_NO_OID, &gname);
275 if (gret != GSS_S_COMPLETE) {
276 dst_gssapi_check_config((char *)array);
278 gss_log(3, "failed gss_import_name: %s",
279 gss_error_tostring(gret, minor, buf,
281 return (ISC_R_FAILURE);
286 /* Get the credentials. */
288 gss_log(3, "acquiring credentials for %s",
289 (char *)gnamebuf.value);
291 /* XXXDCL does this even make any sense? */
292 gss_log(3, "acquiring credentials for ?");
296 usage = GSS_C_INITIATE;
298 usage = GSS_C_ACCEPT;
300 gret = gss_acquire_cred(&minor, gname, GSS_C_INDEFINITE,
302 usage, cred, &mechs, &lifetime);
304 if (gret != GSS_S_COMPLETE) {
305 gss_log(3, "failed to acquire %s credentials for %s: %s",
306 initiate ? "initiate" : "accept",
307 (char *)gnamebuf.value,
308 gss_error_tostring(gret, minor, buf, sizeof(buf)));
309 dst_gssapi_check_config((char *)array);
310 return (ISC_R_FAILURE);
313 gss_log(4, "acquired %s credentials for %s",
314 initiate ? "initiate" : "accept",
315 (char *)gnamebuf.value);
319 return (ISC_R_SUCCESS);
325 return (ISC_R_NOTIMPLEMENTED);
330 dst_gssapi_identitymatchesrealmkrb5(dns_name_t *signer, dns_name_t *name,
334 char sbuf[DNS_NAME_FORMATSIZE];
335 char nbuf[DNS_NAME_FORMATSIZE];
336 char rbuf[DNS_NAME_FORMATSIZE];
342 * It is far, far easier to write the names we are looking at into
343 * a string, and do string operations on them.
345 isc_buffer_init(&buffer, sbuf, sizeof(sbuf));
346 dns_name_toprincipal(signer, &buffer);
347 isc_buffer_putuint8(&buffer, 0);
349 dns_name_format(name, nbuf, sizeof(nbuf));
350 dns_name_format(realm, rbuf, sizeof(rbuf));
353 * Find the realm portion. This is the part after the @. If it
354 * does not exist, we don't have something we like, so we fail our
357 rname = strchr(sbuf, '@');
359 return (isc_boolean_false);
364 * Find the host portion of the signer's name. We do this by
365 * searching for the first / character. We then check to make
366 * certain the instance name is "host"
369 * host/example.com@EXAMPLE.COM
371 sname = strchr(sbuf, '/');
373 return (isc_boolean_false);
376 if (strcmp(sbuf, "host") != 0)
377 return (isc_boolean_false);
380 * Now, we do a simple comparison between the name and the realm.
383 if ((strcasecmp(sname, nbuf) == 0)
384 && (strcmp(rname, rbuf) == 0))
385 return (isc_boolean_true);
387 if (strcmp(rname, rbuf) == 0)
388 return (isc_boolean_true);
391 return (isc_boolean_false);
396 return (isc_boolean_false);
401 dst_gssapi_identitymatchesrealmms(dns_name_t *signer, dns_name_t *name,
405 char sbuf[DNS_NAME_FORMATSIZE];
406 char nbuf[DNS_NAME_FORMATSIZE];
407 char rbuf[DNS_NAME_FORMATSIZE];
414 * It is far, far easier to write the names we are looking at into
415 * a string, and do string operations on them.
417 isc_buffer_init(&buffer, sbuf, sizeof(sbuf));
418 dns_name_toprincipal(signer, &buffer);
419 isc_buffer_putuint8(&buffer, 0);
421 dns_name_format(name, nbuf, sizeof(nbuf));
422 dns_name_format(realm, rbuf, sizeof(rbuf));
425 * Find the realm portion. This is the part after the @. If it
426 * does not exist, we don't have something we like, so we fail our
429 rname = strchr(sbuf, '@');
431 return (isc_boolean_false);
432 sname = strchr(sbuf, '$');
434 return (isc_boolean_false);
437 * Verify that the $ and @ follow one another.
439 if (rname - sname != 1)
440 return (isc_boolean_false);
443 * Find the host portion of the signer's name. Zero out the $ so
444 * it terminates the signer's name, and skip past the @ for
447 * All service principals in Microsoft format seem to be in
448 * machinename$@EXAMPLE.COM
456 * Find the first . in the target name, and make it the end of
457 * the string. The rest of the name has to match the realm.
460 nname = strchr(nbuf, '.');
462 return (isc_boolean_false);
467 * Now, we do a simple comparison between the name and the realm.
470 if ((strcasecmp(sname, nbuf) == 0)
471 && (strcmp(rname, rbuf) == 0)
472 && (strcasecmp(nname, rbuf) == 0))
473 return (isc_boolean_true);
475 if (strcmp(rname, rbuf) == 0)
476 return (isc_boolean_true);
480 return (isc_boolean_false);
485 return (isc_boolean_false);
490 dst_gssapi_releasecred(gss_cred_id_t *cred) {
492 OM_uint32 gret, minor;
495 REQUIRE(cred != NULL && *cred != NULL);
497 gret = gss_release_cred(&minor, cred);
498 if (gret != GSS_S_COMPLETE) {
499 /* Log the error, but still free the credential's memory */
500 gss_log(3, "failed releasing credential: %s",
501 gss_error_tostring(gret, minor, buf, sizeof(buf)));
505 return(ISC_R_SUCCESS);
509 return (ISC_R_NOTIMPLEMENTED);
514 dst_gssapi_initctx(dns_name_t *name, isc_buffer_t *intoken,
515 isc_buffer_t *outtoken, gss_ctx_id_t *gssctx)
519 isc_buffer_t namebuf;
521 OM_uint32 gret, minor, ret_flags, flags;
522 gss_buffer_desc gintoken, *gintokenp, gouttoken = GSS_C_EMPTY_BUFFER;
524 gss_buffer_desc gnamebuf;
525 unsigned char array[DNS_NAME_MAXTEXT + 1];
528 /* Client must pass us a valid gss_ctx_id_t here */
529 REQUIRE(gssctx != NULL);
531 isc_buffer_init(&namebuf, array, sizeof(array));
532 name_to_gbuffer(name, &namebuf, &gnamebuf);
534 /* Get the name as a GSS name */
535 gret = gss_import_name(&minor, &gnamebuf, GSS_C_NO_OID, &gname);
536 if (gret != GSS_S_COMPLETE) {
537 result = ISC_R_FAILURE;
541 if (intoken != NULL) {
542 /* Don't call gss_release_buffer for gintoken! */
543 REGION_TO_GBUFFER(*intoken, gintoken);
544 gintokenp = &gintoken;
550 * Note that we don't set GSS_C_SEQUENCE_FLAG as Windows DNS
551 * servers don't like it.
553 flags = GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_DELEG_FLAG |
556 gret = gss_init_sec_context(&minor, GSS_C_NO_CREDENTIAL, gssctx,
557 gname, GSS_SPNEGO_MECHANISM, flags,
559 NULL, &gouttoken, &ret_flags, NULL);
561 if (gret != GSS_S_COMPLETE && gret != GSS_S_CONTINUE_NEEDED) {
562 gss_log(3, "Failure initiating security context");
563 gss_log(3, "%s", gss_error_tostring(gret, minor,
565 result = ISC_R_FAILURE;
570 * XXXSRA Not handled yet: RFC 3645 3.1.1: check ret_flags
571 * MUTUAL and INTEG flags, fail if either not set.
575 * RFC 2744 states the a valid output token has a non-zero length.
577 if (gouttoken.length != 0) {
578 GBUFFER_TO_REGION(gouttoken, r);
579 RETERR(isc_buffer_copyregion(outtoken, &r));
580 (void)gss_release_buffer(&minor, &gouttoken);
582 (void)gss_release_name(&minor, &gname);
584 if (gret == GSS_S_COMPLETE)
585 result = ISC_R_SUCCESS;
587 result = DNS_R_CONTINUE;
597 return (ISC_R_NOTIMPLEMENTED);
602 dst_gssapi_acceptctx(gss_cred_id_t cred,
603 isc_region_t *intoken, isc_buffer_t **outtoken,
604 gss_ctx_id_t *ctxout, dns_name_t *principal,
609 isc_buffer_t namebuf;
610 gss_buffer_desc gnamebuf = GSS_C_EMPTY_BUFFER, gintoken,
611 gouttoken = GSS_C_EMPTY_BUFFER;
612 OM_uint32 gret, minor;
613 gss_ctx_id_t context = GSS_C_NO_CONTEXT;
614 gss_name_t gname = NULL;
618 REQUIRE(outtoken != NULL && *outtoken == NULL);
622 REGION_TO_GBUFFER(*intoken, gintoken);
625 context = GSS_C_NO_CONTEXT;
629 gret = gss_accept_sec_context(&minor, &context, cred, &gintoken,
630 GSS_C_NO_CHANNEL_BINDINGS, &gname,
631 NULL, &gouttoken, NULL, NULL, NULL);
633 result = ISC_R_FAILURE;
637 result = ISC_R_SUCCESS;
639 case GSS_S_CONTINUE_NEEDED:
640 result = DNS_R_CONTINUE;
642 case GSS_S_DEFECTIVE_TOKEN:
643 case GSS_S_DEFECTIVE_CREDENTIAL:
645 case GSS_S_DUPLICATE_TOKEN:
646 case GSS_S_OLD_TOKEN:
648 case GSS_S_CREDENTIALS_EXPIRED:
649 case GSS_S_BAD_BINDINGS:
650 case GSS_S_NO_CONTEXT:
653 result = DNS_R_INVALIDTKEY;
656 gss_log(3, "failed gss_accept_sec_context: %s",
657 gss_error_tostring(gret, minor, buf, sizeof(buf)));
661 if (gouttoken.length > 0) {
662 RETERR(isc_buffer_allocate(mctx, outtoken, gouttoken.length));
663 GBUFFER_TO_REGION(gouttoken, r);
664 RETERR(isc_buffer_copyregion(*outtoken, &r));
665 (void)gss_release_buffer(&minor, &gouttoken);
668 if (gret == GSS_S_COMPLETE) {
669 gret = gss_display_name(&minor, gname, &gnamebuf, NULL);
670 if (gret != GSS_S_COMPLETE) {
671 gss_log(3, "failed gss_display_name: %s",
672 gss_error_tostring(gret, minor,
674 RETERR(ISC_R_FAILURE);
678 * Compensate for a bug in Solaris8's implementation
679 * of gss_display_name(). Should be harmless in any
680 * case, since principal names really should not
681 * contain null characters.
683 if (gnamebuf.length > 0 &&
684 ((char *)gnamebuf.value)[gnamebuf.length - 1] == '\0')
687 gss_log(3, "gss-api source name (accept) is %.*s",
688 (int)gnamebuf.length, (char *)gnamebuf.value);
690 GBUFFER_TO_REGION(gnamebuf, r);
691 isc_buffer_init(&namebuf, r.base, r.length);
692 isc_buffer_add(&namebuf, r.length);
694 RETERR(dns_name_fromtext(principal, &namebuf, dns_rootname,
697 if (gnamebuf.length != 0) {
698 gret = gss_release_buffer(&minor, &gnamebuf);
699 if (gret != GSS_S_COMPLETE)
700 gss_log(3, "failed gss_release_buffer: %s",
701 gss_error_tostring(gret, minor, buf,
710 gret = gss_release_name(&minor, &gname);
711 if (gret != GSS_S_COMPLETE)
712 gss_log(3, "failed gss_release_name: %s",
713 gss_error_tostring(gret, minor, buf,
726 return (ISC_R_NOTIMPLEMENTED);
731 dst_gssapi_deletectx(isc_mem_t *mctx, gss_ctx_id_t *gssctx)
734 OM_uint32 gret, minor;
739 REQUIRE(gssctx != NULL && *gssctx != NULL);
741 /* Delete the context from the GSS provider */
742 gret = gss_delete_sec_context(&minor, gssctx, GSS_C_NO_BUFFER);
743 if (gret != GSS_S_COMPLETE) {
744 /* Log the error, but still free the context's memory */
745 gss_log(3, "Failure deleting security context %s",
746 gss_error_tostring(gret, minor, buf, sizeof(buf)));
748 return(ISC_R_SUCCESS);
752 return (ISC_R_NOTIMPLEMENTED);
757 gss_error_tostring(isc_uint32_t major, isc_uint32_t minor,
758 char *buf, size_t buflen) {
760 gss_buffer_desc msg_minor = GSS_C_EMPTY_BUFFER,
761 msg_major = GSS_C_EMPTY_BUFFER;
762 OM_uint32 msg_ctx, minor_stat;
764 /* Handle major status */
766 (void)gss_display_status(&minor_stat, major, GSS_C_GSS_CODE,
767 GSS_C_NULL_OID, &msg_ctx, &msg_major);
769 /* Handle minor status */
771 (void)gss_display_status(&minor_stat, minor, GSS_C_MECH_CODE,
772 GSS_C_NULL_OID, &msg_ctx, &msg_minor);
774 snprintf(buf, buflen, "GSSAPI error: Major = %s, Minor = %s.",
775 (char *)msg_major.value, (char *)msg_minor.value);
777 if (msg_major.length != 0)
778 (void)gss_release_buffer(&minor_stat, &msg_major);
779 if (msg_minor.length != 0)
780 (void)gss_release_buffer(&minor_stat, &msg_minor);
783 snprintf(buf, buflen, "GSSAPI error: Major = %u, Minor = %u.",
791 gss_log(int level, const char *fmt, ...) {
795 isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_GENERAL,
796 DNS_LOGMODULE_TKEY, ISC_LOG_DEBUG(level), fmt, ap);