2 * Copyright (C) 2004-2012 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.
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 RUNTIME_CHECK(result == ISC_R_SUCCESS);
139 isc_buffer_putuint8(buffer, 0);
140 isc_buffer_usedregion(buffer, &r);
141 REGION_TO_GBUFFER(r, *gbuffer);
145 log_cred(const gss_cred_id_t cred) {
146 OM_uint32 gret, minor, lifetime;
148 gss_buffer_desc gbuffer;
149 gss_cred_usage_t usage;
150 const char *usage_text;
153 gret = gss_inquire_cred(&minor, cred, &gname, &lifetime, &usage, NULL);
154 if (gret != GSS_S_COMPLETE) {
155 gss_log(3, "failed gss_inquire_cred: %s",
156 gss_error_tostring(gret, minor, buf, sizeof(buf)));
160 gret = gss_display_name(&minor, gname, &gbuffer, NULL);
161 if (gret != GSS_S_COMPLETE)
162 gss_log(3, "failed gss_display_name: %s",
163 gss_error_tostring(gret, minor, buf, sizeof(buf)));
167 usage_text = "GSS_C_BOTH";
170 usage_text = "GSS_C_INITIATE";
173 usage_text = "GSS_C_ACCEPT";
178 gss_log(3, "gss cred: \"%s\", %s, %lu", (char *)gbuffer.value,
179 usage_text, (unsigned long)lifetime);
182 if (gret == GSS_S_COMPLETE) {
183 if (gbuffer.length != 0U) {
184 gret = gss_release_buffer(&minor, &gbuffer);
185 if (gret != GSS_S_COMPLETE)
186 gss_log(3, "failed gss_release_buffer: %s",
187 gss_error_tostring(gret, minor, buf,
192 gret = gss_release_name(&minor, &gname);
193 if (gret != GSS_S_COMPLETE)
194 gss_log(3, "failed gss_release_name: %s",
195 gss_error_tostring(gret, minor, buf, sizeof(buf)));
201 * check for the most common configuration errors.
203 * The errors checked for are:
204 * - tkey-gssapi-credential doesn't start with DNS/
205 * - the default realm in /etc/krb5.conf and the
206 * tkey-gssapi-credential bind config option don't match
208 * Note that if tkey-gssapi-keytab is set then these configure checks
209 * are not performed, and runtime errors from gssapi are used instead
212 check_config(const char *gss_name) {
214 krb5_context krb5_ctx;
215 char *krb5_realm = NULL;
217 if (strncasecmp(gss_name, "DNS/", 4) != 0) {
218 gss_log(ISC_LOG_ERROR, "tkey-gssapi-credential (%s) "
219 "should start with 'DNS/'", gss_name);
223 if (krb5_init_context(&krb5_ctx) != 0) {
224 gss_log(ISC_LOG_ERROR, "Unable to initialise krb5 context");
227 if (krb5_get_default_realm(krb5_ctx, &krb5_realm) != 0) {
228 gss_log(ISC_LOG_ERROR, "Unable to get krb5 default realm");
229 krb5_free_context(krb5_ctx);
232 p = strchr(gss_name, '/');
234 gss_log(ISC_LOG_ERROR, "badly formatted "
235 "tkey-gssapi-credentials (%s)", gss_name);
236 krb5_free_context(krb5_ctx);
239 if (strcasecmp(p + 1, krb5_realm) != 0) {
240 gss_log(ISC_LOG_ERROR, "default realm from krb5.conf (%s) "
241 "does not match tkey-gssapi-credential (%s)",
242 krb5_realm, gss_name);
243 krb5_free_context(krb5_ctx);
246 krb5_free_context(krb5_ctx);
251 dst_gssapi_acquirecred(dns_name_t *name, isc_boolean_t initiate,
255 isc_buffer_t namebuf;
257 gss_buffer_desc gnamebuf;
258 unsigned char array[DNS_NAME_MAXTEXT + 1];
259 OM_uint32 gret, minor;
262 gss_cred_usage_t usage;
265 REQUIRE(cred != NULL && *cred == NULL);
268 * XXXSRA In theory we could use GSS_C_NT_HOSTBASED_SERVICE
269 * here when we're in the acceptor role, which would let us
270 * default the hostname and use a compiled in default service
271 * name of "DNS", giving one less thing to configure in
272 * named.conf. Unfortunately, this creates a circular
273 * dependency due to DNS-based realm lookup in at least one
274 * GSSAPI implementation (Heimdal). Oh well.
277 isc_buffer_init(&namebuf, array, sizeof(array));
278 name_to_gbuffer(name, &namebuf, &gnamebuf);
279 gret = gss_import_name(&minor, &gnamebuf,
280 GSS_C_NO_OID, &gname);
281 if (gret != GSS_S_COMPLETE) {
282 check_config((char *)array);
284 gss_log(3, "failed gss_import_name: %s",
285 gss_error_tostring(gret, minor, buf,
287 return (ISC_R_FAILURE);
292 /* Get the credentials. */
294 gss_log(3, "acquiring credentials for %s",
295 (char *)gnamebuf.value);
297 /* XXXDCL does this even make any sense? */
298 gss_log(3, "acquiring credentials for ?");
302 usage = GSS_C_INITIATE;
304 usage = GSS_C_ACCEPT;
306 gret = gss_acquire_cred(&minor, gname, GSS_C_INDEFINITE,
308 usage, cred, &mechs, &lifetime);
310 if (gret != GSS_S_COMPLETE) {
311 gss_log(3, "failed to acquire %s credentials for %s: %s",
312 initiate ? "initiate" : "accept",
313 (gname != NULL) ? (char *)gnamebuf.value : "?",
314 gss_error_tostring(gret, minor, buf, sizeof(buf)));
315 check_config((char *)array);
316 return (ISC_R_FAILURE);
319 gss_log(4, "acquired %s credentials for %s",
320 initiate ? "initiate" : "accept",
321 (gname != NULL) ? (char *)gnamebuf.value : "?");
325 return (ISC_R_SUCCESS);
327 REQUIRE(cred != NULL && *cred == NULL);
333 return (ISC_R_NOTIMPLEMENTED);
338 dst_gssapi_identitymatchesrealmkrb5(dns_name_t *signer, dns_name_t *name,
342 char sbuf[DNS_NAME_FORMATSIZE];
343 char nbuf[DNS_NAME_FORMATSIZE];
344 char rbuf[DNS_NAME_FORMATSIZE];
351 * It is far, far easier to write the names we are looking at into
352 * a string, and do string operations on them.
354 isc_buffer_init(&buffer, sbuf, sizeof(sbuf));
355 result = dns_name_toprincipal(signer, &buffer);
356 RUNTIME_CHECK(result == ISC_R_SUCCESS);
357 isc_buffer_putuint8(&buffer, 0);
359 dns_name_format(name, nbuf, sizeof(nbuf));
360 dns_name_format(realm, rbuf, sizeof(rbuf));
363 * Find the realm portion. This is the part after the @. If it
364 * does not exist, we don't have something we like, so we fail our
367 rname = strchr(sbuf, '@');
369 return (isc_boolean_false);
374 * Find the host portion of the signer's name. We do this by
375 * searching for the first / character. We then check to make
376 * certain the instance name is "host"
379 * host/example.com@EXAMPLE.COM
381 sname = strchr(sbuf, '/');
383 return (isc_boolean_false);
386 if (strcmp(sbuf, "host") != 0)
387 return (isc_boolean_false);
390 * Now, we do a simple comparison between the name and the realm.
393 if ((strcasecmp(sname, nbuf) == 0)
394 && (strcmp(rname, rbuf) == 0))
395 return (isc_boolean_true);
397 if (strcmp(rname, rbuf) == 0)
398 return (isc_boolean_true);
401 return (isc_boolean_false);
406 return (isc_boolean_false);
411 dst_gssapi_identitymatchesrealmms(dns_name_t *signer, dns_name_t *name,
415 char sbuf[DNS_NAME_FORMATSIZE];
416 char nbuf[DNS_NAME_FORMATSIZE];
417 char rbuf[DNS_NAME_FORMATSIZE];
425 * It is far, far easier to write the names we are looking at into
426 * a string, and do string operations on them.
428 isc_buffer_init(&buffer, sbuf, sizeof(sbuf));
429 result = dns_name_toprincipal(signer, &buffer);
430 RUNTIME_CHECK(result == ISC_R_SUCCESS);
431 isc_buffer_putuint8(&buffer, 0);
433 dns_name_format(name, nbuf, sizeof(nbuf));
434 dns_name_format(realm, rbuf, sizeof(rbuf));
437 * Find the realm portion. This is the part after the @. If it
438 * does not exist, we don't have something we like, so we fail our
441 rname = strchr(sbuf, '@');
443 return (isc_boolean_false);
444 sname = strchr(sbuf, '$');
446 return (isc_boolean_false);
449 * Verify that the $ and @ follow one another.
451 if (rname - sname != 1)
452 return (isc_boolean_false);
455 * Find the host portion of the signer's name. Zero out the $ so
456 * it terminates the signer's name, and skip past the @ for
459 * All service principals in Microsoft format seem to be in
460 * machinename$@EXAMPLE.COM
468 * Find the first . in the target name, and make it the end of
469 * the string. The rest of the name has to match the realm.
472 nname = strchr(nbuf, '.');
474 return (isc_boolean_false);
479 * Now, we do a simple comparison between the name and the realm.
482 if ((strcasecmp(sname, nbuf) == 0)
483 && (strcmp(rname, rbuf) == 0)
484 && (strcasecmp(nname, rbuf) == 0))
485 return (isc_boolean_true);
487 if (strcmp(rname, rbuf) == 0)
488 return (isc_boolean_true);
492 return (isc_boolean_false);
497 return (isc_boolean_false);
502 dst_gssapi_releasecred(gss_cred_id_t *cred) {
504 OM_uint32 gret, minor;
507 REQUIRE(cred != NULL && *cred != NULL);
509 gret = gss_release_cred(&minor, cred);
510 if (gret != GSS_S_COMPLETE) {
511 /* Log the error, but still free the credential's memory */
512 gss_log(3, "failed releasing credential: %s",
513 gss_error_tostring(gret, minor, buf, sizeof(buf)));
517 return(ISC_R_SUCCESS);
521 return (ISC_R_NOTIMPLEMENTED);
527 * Format a gssapi error message info into a char ** on the given memory
528 * context. This is used to return gssapi error messages back up the
529 * call chain for reporting to the user.
532 gss_err_message(isc_mem_t *mctx, isc_uint32_t major, isc_uint32_t minor,
538 if (err_message == NULL || mctx == NULL) {
539 /* the caller doesn't want any error messages */
543 estr = gss_error_tostring(major, minor, buf, sizeof(buf));
545 (*err_message) = isc_mem_strdup(mctx, estr);
550 dst_gssapi_initctx(dns_name_t *name, isc_buffer_t *intoken,
551 isc_buffer_t *outtoken, gss_ctx_id_t *gssctx,
552 isc_mem_t *mctx, char **err_message)
556 isc_buffer_t namebuf;
558 OM_uint32 gret, minor, ret_flags, flags;
559 gss_buffer_desc gintoken, *gintokenp, gouttoken = GSS_C_EMPTY_BUFFER;
561 gss_buffer_desc gnamebuf;
562 unsigned char array[DNS_NAME_MAXTEXT + 1];
564 /* Client must pass us a valid gss_ctx_id_t here */
565 REQUIRE(gssctx != NULL);
566 REQUIRE(mctx != NULL);
568 isc_buffer_init(&namebuf, array, sizeof(array));
569 name_to_gbuffer(name, &namebuf, &gnamebuf);
571 /* Get the name as a GSS name */
572 gret = gss_import_name(&minor, &gnamebuf, GSS_C_NO_OID, &gname);
573 if (gret != GSS_S_COMPLETE) {
574 gss_err_message(mctx, gret, minor, err_message);
575 result = ISC_R_FAILURE;
579 if (intoken != NULL) {
580 /* Don't call gss_release_buffer for gintoken! */
581 REGION_TO_GBUFFER(*intoken, gintoken);
582 gintokenp = &gintoken;
588 * Note that we don't set GSS_C_SEQUENCE_FLAG as Windows DNS
589 * servers don't like it.
591 flags = GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG;
593 gret = gss_init_sec_context(&minor, GSS_C_NO_CREDENTIAL, gssctx,
594 gname, GSS_SPNEGO_MECHANISM, flags,
596 NULL, &gouttoken, &ret_flags, NULL);
598 if (gret != GSS_S_COMPLETE && gret != GSS_S_CONTINUE_NEEDED) {
599 gss_err_message(mctx, gret, minor, err_message);
600 if (err_message != NULL && *err_message != NULL)
601 gss_log(3, "Failure initiating security context: %s",
604 gss_log(3, "Failure initiating security context");
606 result = ISC_R_FAILURE;
611 * XXXSRA Not handled yet: RFC 3645 3.1.1: check ret_flags
612 * MUTUAL and INTEG flags, fail if either not set.
616 * RFC 2744 states the a valid output token has a non-zero length.
618 if (gouttoken.length != 0U) {
619 GBUFFER_TO_REGION(gouttoken, r);
620 RETERR(isc_buffer_copyregion(outtoken, &r));
621 (void)gss_release_buffer(&minor, &gouttoken);
623 (void)gss_release_name(&minor, &gname);
625 if (gret == GSS_S_COMPLETE)
626 result = ISC_R_SUCCESS;
628 result = DNS_R_CONTINUE;
640 return (ISC_R_NOTIMPLEMENTED);
645 dst_gssapi_acceptctx(gss_cred_id_t cred,
646 const char *gssapi_keytab,
647 isc_region_t *intoken, isc_buffer_t **outtoken,
648 gss_ctx_id_t *ctxout, dns_name_t *principal,
653 isc_buffer_t namebuf;
654 gss_buffer_desc gnamebuf = GSS_C_EMPTY_BUFFER, gintoken,
655 gouttoken = GSS_C_EMPTY_BUFFER;
656 OM_uint32 gret, minor;
657 gss_ctx_id_t context = GSS_C_NO_CONTEXT;
658 gss_name_t gname = NULL;
662 REQUIRE(outtoken != NULL && *outtoken == NULL);
664 REGION_TO_GBUFFER(*intoken, gintoken);
667 context = GSS_C_NO_CONTEXT;
671 if (gssapi_keytab != NULL) {
672 #ifdef ISC_PLATFORM_GSSAPI_KRB5_HEADER
673 gret = gsskrb5_register_acceptor_identity(gssapi_keytab);
674 if (gret != GSS_S_COMPLETE) {
676 "gsskrb5_register_acceptor_identity(%s): %s",
678 gss_error_tostring(gret, 0, buf, sizeof(buf)));
679 return (DNS_R_INVALIDTKEY);
683 * Minimize memory leakage by only setting KRB5_KTNAME
684 * if it needs to change.
686 const char *old = getenv("KRB5_KTNAME");
687 if (old == NULL || strcmp(old, gssapi_keytab) != 0) {
688 char *kt = malloc(strlen(gssapi_keytab) + 13);
690 return (ISC_R_NOMEMORY);
691 sprintf(kt, "KRB5_KTNAME=%s", gssapi_keytab);
693 return (ISC_R_NOMEMORY);
700 gret = gss_accept_sec_context(&minor, &context, cred, &gintoken,
701 GSS_C_NO_CHANNEL_BINDINGS, &gname,
702 NULL, &gouttoken, NULL, NULL, NULL);
704 result = ISC_R_FAILURE;
708 result = ISC_R_SUCCESS;
710 case GSS_S_CONTINUE_NEEDED:
711 result = DNS_R_CONTINUE;
713 case GSS_S_DEFECTIVE_TOKEN:
714 case GSS_S_DEFECTIVE_CREDENTIAL:
716 case GSS_S_DUPLICATE_TOKEN:
717 case GSS_S_OLD_TOKEN:
719 case GSS_S_CREDENTIALS_EXPIRED:
720 case GSS_S_BAD_BINDINGS:
721 case GSS_S_NO_CONTEXT:
724 result = DNS_R_INVALIDTKEY;
727 gss_log(3, "failed gss_accept_sec_context: %s",
728 gss_error_tostring(gret, minor, buf, sizeof(buf)));
732 if (gouttoken.length > 0U) {
733 RETERR(isc_buffer_allocate(mctx, outtoken, gouttoken.length));
734 GBUFFER_TO_REGION(gouttoken, r);
735 RETERR(isc_buffer_copyregion(*outtoken, &r));
736 (void)gss_release_buffer(&minor, &gouttoken);
739 if (gret == GSS_S_COMPLETE) {
740 gret = gss_display_name(&minor, gname, &gnamebuf, NULL);
741 if (gret != GSS_S_COMPLETE) {
742 gss_log(3, "failed gss_display_name: %s",
743 gss_error_tostring(gret, minor,
745 RETERR(ISC_R_FAILURE);
749 * Compensate for a bug in Solaris8's implementation
750 * of gss_display_name(). Should be harmless in any
751 * case, since principal names really should not
752 * contain null characters.
754 if (gnamebuf.length > 0U &&
755 ((char *)gnamebuf.value)[gnamebuf.length - 1] == '\0')
758 gss_log(3, "gss-api source name (accept) is %.*s",
759 (int)gnamebuf.length, (char *)gnamebuf.value);
761 GBUFFER_TO_REGION(gnamebuf, r);
762 isc_buffer_init(&namebuf, r.base, r.length);
763 isc_buffer_add(&namebuf, r.length);
765 RETERR(dns_name_fromtext(principal, &namebuf, dns_rootname,
768 if (gnamebuf.length != 0U) {
769 gret = gss_release_buffer(&minor, &gnamebuf);
770 if (gret != GSS_S_COMPLETE)
771 gss_log(3, "failed gss_release_buffer: %s",
772 gss_error_tostring(gret, minor, buf,
781 gret = gss_release_name(&minor, &gname);
782 if (gret != GSS_S_COMPLETE)
783 gss_log(3, "failed gss_release_name: %s",
784 gss_error_tostring(gret, minor, buf,
791 UNUSED(gssapi_keytab);
798 return (ISC_R_NOTIMPLEMENTED);
803 dst_gssapi_deletectx(isc_mem_t *mctx, gss_ctx_id_t *gssctx)
806 OM_uint32 gret, minor;
811 REQUIRE(gssctx != NULL && *gssctx != NULL);
813 /* Delete the context from the GSS provider */
814 gret = gss_delete_sec_context(&minor, gssctx, GSS_C_NO_BUFFER);
815 if (gret != GSS_S_COMPLETE) {
816 /* Log the error, but still free the context's memory */
817 gss_log(3, "Failure deleting security context %s",
818 gss_error_tostring(gret, minor, buf, sizeof(buf)));
820 return(ISC_R_SUCCESS);
824 return (ISC_R_NOTIMPLEMENTED);
829 gss_error_tostring(isc_uint32_t major, isc_uint32_t minor,
830 char *buf, size_t buflen) {
832 gss_buffer_desc msg_minor = GSS_C_EMPTY_BUFFER,
833 msg_major = GSS_C_EMPTY_BUFFER;
834 OM_uint32 msg_ctx, minor_stat;
836 /* Handle major status */
838 (void)gss_display_status(&minor_stat, major, GSS_C_GSS_CODE,
839 GSS_C_NULL_OID, &msg_ctx, &msg_major);
841 /* Handle minor status */
843 (void)gss_display_status(&minor_stat, minor, GSS_C_MECH_CODE,
844 GSS_C_NULL_OID, &msg_ctx, &msg_minor);
846 snprintf(buf, buflen, "GSSAPI error: Major = %s, Minor = %s.",
847 (char *)msg_major.value, (char *)msg_minor.value);
849 if (msg_major.length != 0U)
850 (void)gss_release_buffer(&minor_stat, &msg_major);
851 if (msg_minor.length != 0U)
852 (void)gss_release_buffer(&minor_stat, &msg_minor);
855 snprintf(buf, buflen, "GSSAPI error: Major = %u, Minor = %u.",
863 gss_log(int level, const char *fmt, ...) {
867 isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_GENERAL,
868 DNS_LOGMODULE_TKEY, ISC_LOG_DEBUG(level), fmt, ap);