4 * Copyright (C) 2004-2009 Internet Systems Consortium, Inc. ("ISC")
5 * Copyright (C) 2000, 2001 Internet Software Consortium.
7 * Permission to use, copy, modify, and/or distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
11 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
12 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
13 * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
14 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
15 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
16 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17 * PERFORMANCE OF THIS SOFTWARE.
20 /* Id: gssapictx.c,v 1.14 2009/09/02 23:48:02 tbox Exp */
27 #include <isc/buffer.h>
29 #include <isc/entropy.h>
33 #include <isc/print.h>
34 #include <isc/random.h>
35 #include <isc/string.h>
39 #include <dns/fixedname.h>
41 #include <dns/rdata.h>
42 #include <dns/rdataclass.h>
43 #include <dns/result.h>
44 #include <dns/types.h>
45 #include <dns/keyvalues.h>
48 #include <dst/gssapi.h>
49 #include <dst/result.h>
51 #include "dst_internal.h"
54 * If we're using our own SPNEGO implementation (see configure.in),
55 * pull it in now. Otherwise, we just use whatever GSSAPI supplies.
57 #if defined(GSSAPI) && defined(USE_ISC_SPNEGO)
59 #define gss_accept_sec_context gss_accept_sec_context_spnego
60 #define gss_init_sec_context gss_init_sec_context_spnego
64 * Solaris8 apparently needs an explicit OID set, and Solaris10 needs
65 * one for anything but Kerberos. Supplying an explicit OID set
66 * doesn't appear to hurt anything in other implementations, so we
67 * always use one. If we're not using our own SPNEGO implementation,
68 * we include SPNEGO's OID.
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_totext(namep
, ISC_FALSE
, 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
)));
197 dst_gssapi_acquirecred(dns_name_t
*name
, isc_boolean_t initiate
,
201 isc_buffer_t namebuf
;
203 gss_buffer_desc gnamebuf
;
204 unsigned char array
[DNS_NAME_MAXTEXT
+ 1];
205 OM_uint32 gret
, minor
;
208 gss_cred_usage_t usage
;
211 REQUIRE(cred
!= NULL
&& *cred
== NULL
);
214 * XXXSRA In theory we could use GSS_C_NT_HOSTBASED_SERVICE
215 * here when we're in the acceptor role, which would let us
216 * default the hostname and use a compiled in default service
217 * name of "DNS", giving one less thing to configure in
218 * named.conf. Unfortunately, this creates a circular
219 * dependency due to DNS-based realm lookup in at least one
220 * GSSAPI implementation (Heimdal). Oh well.
223 isc_buffer_init(&namebuf
, array
, sizeof(array
));
224 name_to_gbuffer(name
, &namebuf
, &gnamebuf
);
225 gret
= gss_import_name(&minor
, &gnamebuf
,
226 GSS_C_NO_OID
, &gname
);
227 if (gret
!= GSS_S_COMPLETE
) {
228 gss_log(3, "failed gss_import_name: %s",
229 gss_error_tostring(gret
, minor
, buf
,
231 return (ISC_R_FAILURE
);
236 /* Get the credentials. */
238 gss_log(3, "acquiring credentials for %s",
239 (char *)gnamebuf
.value
);
241 /* XXXDCL does this even make any sense? */
242 gss_log(3, "acquiring credentials for ?");
246 usage
= GSS_C_INITIATE
;
248 usage
= GSS_C_ACCEPT
;
250 gret
= gss_acquire_cred(&minor
, gname
, GSS_C_INDEFINITE
,
252 usage
, cred
, &mechs
, &lifetime
);
254 if (gret
!= GSS_S_COMPLETE
) {
255 gss_log(3, "failed to acquire %s credentials for %s: %s",
256 initiate
? "initiate" : "accept",
257 (char *)gnamebuf
.value
,
258 gss_error_tostring(gret
, minor
, buf
, sizeof(buf
)));
259 return (ISC_R_FAILURE
);
262 gss_log(4, "acquired %s credentials for %s",
263 initiate
? "initiate" : "accept",
264 (char *)gnamebuf
.value
);
268 return (ISC_R_SUCCESS
);
274 return (ISC_R_NOTIMPLEMENTED
);
279 dst_gssapi_identitymatchesrealmkrb5(dns_name_t
*signer
, dns_name_t
*name
,
283 char sbuf
[DNS_NAME_FORMATSIZE
];
284 char nbuf
[DNS_NAME_FORMATSIZE
];
285 char rbuf
[DNS_NAME_FORMATSIZE
];
290 * It is far, far easier to write the names we are looking at into
291 * a string, and do string operations on them.
293 dns_name_format(signer
, sbuf
, sizeof(sbuf
));
295 dns_name_format(name
, nbuf
, sizeof(nbuf
));
296 dns_name_format(realm
, rbuf
, sizeof(rbuf
));
299 * Find the realm portion. This is the part after the @. If it
300 * does not exist, we don't have something we like, so we fail our
303 rname
= strstr(sbuf
, "\\@");
305 return (isc_boolean_false
);
310 * Find the host portion of the signer's name. We do this by
311 * searching for the first / character. We then check to make
312 * certain the instance name is "host"
315 * host/example.com@EXAMPLE.COM
317 sname
= strchr(sbuf
, '/');
319 return (isc_boolean_false
);
322 if (strcmp(sbuf
, "host") != 0)
323 return (isc_boolean_false
);
326 * Now, we do a simple comparison between the name and the realm.
329 if ((strcasecmp(sname
, nbuf
) == 0)
330 && (strcmp(rname
, rbuf
) == 0))
331 return (isc_boolean_true
);
333 if (strcmp(rname
, rbuf
) == 0)
334 return (isc_boolean_true
);
337 return (isc_boolean_false
);
342 return (isc_boolean_false
);
347 dst_gssapi_identitymatchesrealmms(dns_name_t
*signer
, dns_name_t
*name
,
351 char sbuf
[DNS_NAME_FORMATSIZE
];
352 char nbuf
[DNS_NAME_FORMATSIZE
];
353 char rbuf
[DNS_NAME_FORMATSIZE
];
359 * It is far, far easier to write the names we are looking at into
360 * a string, and do string operations on them.
362 dns_name_format(signer
, sbuf
, sizeof(sbuf
));
364 dns_name_format(name
, nbuf
, sizeof(nbuf
));
365 dns_name_format(realm
, rbuf
, sizeof(rbuf
));
368 * Find the realm portion. This is the part after the @. If it
369 * does not exist, we don't have something we like, so we fail our
372 rname
= strstr(sbuf
, "\\@");
374 return (isc_boolean_false
);
375 sname
= strstr(sbuf
, "\\$");
377 return (isc_boolean_false
);
380 * Verify that the $ and @ follow one another.
382 if (rname
- sname
!= 2)
383 return (isc_boolean_false
);
386 * Find the host portion of the signer's name. Zero out the $ so
387 * it terminates the signer's name, and skip past the @ for
390 * All service principals in Microsoft format seem to be in
391 * machinename$@EXAMPLE.COM
400 * Find the first . in the target name, and make it the end of
401 * the string. The rest of the name has to match the realm.
404 nname
= strchr(nbuf
, '.');
406 return (isc_boolean_false
);
411 * Now, we do a simple comparison between the name and the realm.
414 if ((strcasecmp(sname
, nbuf
) == 0)
415 && (strcmp(rname
, rbuf
) == 0)
416 && (strcasecmp(nname
, rbuf
) == 0))
417 return (isc_boolean_true
);
419 if (strcmp(rname
, rbuf
) == 0)
420 return (isc_boolean_true
);
424 return (isc_boolean_false
);
429 return (isc_boolean_false
);
434 dst_gssapi_releasecred(gss_cred_id_t
*cred
) {
436 OM_uint32 gret
, minor
;
439 REQUIRE(cred
!= NULL
&& *cred
!= NULL
);
441 gret
= gss_release_cred(&minor
, cred
);
442 if (gret
!= GSS_S_COMPLETE
) {
443 /* Log the error, but still free the credential's memory */
444 gss_log(3, "failed releasing credential: %s",
445 gss_error_tostring(gret
, minor
, buf
, sizeof(buf
)));
449 return(ISC_R_SUCCESS
);
453 return (ISC_R_NOTIMPLEMENTED
);
458 dst_gssapi_initctx(dns_name_t
*name
, isc_buffer_t
*intoken
,
459 isc_buffer_t
*outtoken
, gss_ctx_id_t
*gssctx
)
463 isc_buffer_t namebuf
;
465 OM_uint32 gret
, minor
, ret_flags
, flags
;
466 gss_buffer_desc gintoken
, *gintokenp
, gouttoken
= GSS_C_EMPTY_BUFFER
;
468 gss_buffer_desc gnamebuf
;
469 unsigned char array
[DNS_NAME_MAXTEXT
+ 1];
472 /* Client must pass us a valid gss_ctx_id_t here */
473 REQUIRE(gssctx
!= NULL
);
475 isc_buffer_init(&namebuf
, array
, sizeof(array
));
476 name_to_gbuffer(name
, &namebuf
, &gnamebuf
);
478 /* Get the name as a GSS name */
479 gret
= gss_import_name(&minor
, &gnamebuf
, GSS_C_NO_OID
, &gname
);
480 if (gret
!= GSS_S_COMPLETE
) {
481 result
= ISC_R_FAILURE
;
485 if (intoken
!= NULL
) {
486 /* Don't call gss_release_buffer for gintoken! */
487 REGION_TO_GBUFFER(*intoken
, gintoken
);
488 gintokenp
= &gintoken
;
493 flags
= GSS_C_REPLAY_FLAG
| GSS_C_MUTUAL_FLAG
| GSS_C_DELEG_FLAG
|
494 GSS_C_SEQUENCE_FLAG
| GSS_C_INTEG_FLAG
;
496 gret
= gss_init_sec_context(&minor
, GSS_C_NO_CREDENTIAL
, gssctx
,
497 gname
, GSS_SPNEGO_MECHANISM
, flags
,
499 NULL
, &gouttoken
, &ret_flags
, NULL
);
501 if (gret
!= GSS_S_COMPLETE
&& gret
!= GSS_S_CONTINUE_NEEDED
) {
502 gss_log(3, "Failure initiating security context");
503 gss_log(3, "%s", gss_error_tostring(gret
, minor
,
505 result
= ISC_R_FAILURE
;
510 * XXXSRA Not handled yet: RFC 3645 3.1.1: check ret_flags
511 * MUTUAL and INTEG flags, fail if either not set.
515 * RFC 2744 states the a valid output token has a non-zero length.
517 if (gouttoken
.length
!= 0) {
518 GBUFFER_TO_REGION(gouttoken
, r
);
519 RETERR(isc_buffer_copyregion(outtoken
, &r
));
520 (void)gss_release_buffer(&minor
, &gouttoken
);
522 (void)gss_release_name(&minor
, &gname
);
524 if (gret
== GSS_S_COMPLETE
)
525 result
= ISC_R_SUCCESS
;
527 result
= DNS_R_CONTINUE
;
537 return (ISC_R_NOTIMPLEMENTED
);
542 dst_gssapi_acceptctx(gss_cred_id_t cred
,
543 isc_region_t
*intoken
, isc_buffer_t
**outtoken
,
544 gss_ctx_id_t
*ctxout
, dns_name_t
*principal
,
549 isc_buffer_t namebuf
;
550 gss_buffer_desc gnamebuf
= GSS_C_EMPTY_BUFFER
, gintoken
,
551 gouttoken
= GSS_C_EMPTY_BUFFER
;
552 OM_uint32 gret
, minor
;
553 gss_ctx_id_t context
= GSS_C_NO_CONTEXT
;
554 gss_name_t gname
= NULL
;
558 REQUIRE(outtoken
!= NULL
&& *outtoken
== NULL
);
562 REGION_TO_GBUFFER(*intoken
, gintoken
);
565 context
= GSS_C_NO_CONTEXT
;
569 gret
= gss_accept_sec_context(&minor
, &context
, cred
, &gintoken
,
570 GSS_C_NO_CHANNEL_BINDINGS
, &gname
,
571 NULL
, &gouttoken
, NULL
, NULL
, NULL
);
573 result
= ISC_R_FAILURE
;
577 result
= ISC_R_SUCCESS
;
579 case GSS_S_CONTINUE_NEEDED
:
580 result
= DNS_R_CONTINUE
;
582 case GSS_S_DEFECTIVE_TOKEN
:
583 case GSS_S_DEFECTIVE_CREDENTIAL
:
585 case GSS_S_DUPLICATE_TOKEN
:
586 case GSS_S_OLD_TOKEN
:
588 case GSS_S_CREDENTIALS_EXPIRED
:
589 case GSS_S_BAD_BINDINGS
:
590 case GSS_S_NO_CONTEXT
:
593 result
= DNS_R_INVALIDTKEY
;
596 gss_log(3, "failed gss_accept_sec_context: %s",
597 gss_error_tostring(gret
, minor
, buf
, sizeof(buf
)));
601 if (gouttoken
.length
> 0) {
602 RETERR(isc_buffer_allocate(mctx
, outtoken
, gouttoken
.length
));
603 GBUFFER_TO_REGION(gouttoken
, r
);
604 RETERR(isc_buffer_copyregion(*outtoken
, &r
));
605 (void)gss_release_buffer(&minor
, &gouttoken
);
608 if (gret
== GSS_S_COMPLETE
) {
609 gret
= gss_display_name(&minor
, gname
, &gnamebuf
, NULL
);
610 if (gret
!= GSS_S_COMPLETE
) {
611 gss_log(3, "failed gss_display_name: %s",
612 gss_error_tostring(gret
, minor
,
614 RETERR(ISC_R_FAILURE
);
618 * Compensate for a bug in Solaris8's implementation
619 * of gss_display_name(). Should be harmless in any
620 * case, since principal names really should not
621 * contain null characters.
623 if (gnamebuf
.length
> 0 &&
624 ((char *)gnamebuf
.value
)[gnamebuf
.length
- 1] == '\0')
627 gss_log(3, "gss-api source name (accept) is %.*s",
628 (int)gnamebuf
.length
, (char *)gnamebuf
.value
);
630 GBUFFER_TO_REGION(gnamebuf
, r
);
631 isc_buffer_init(&namebuf
, r
.base
, r
.length
);
632 isc_buffer_add(&namebuf
, r
.length
);
634 RETERR(dns_name_fromtext(principal
, &namebuf
, dns_rootname
,
637 if (gnamebuf
.length
!= 0) {
638 gret
= gss_release_buffer(&minor
, &gnamebuf
);
639 if (gret
!= GSS_S_COMPLETE
)
640 gss_log(3, "failed gss_release_buffer: %s",
641 gss_error_tostring(gret
, minor
, buf
,
650 gret
= gss_release_name(&minor
, &gname
);
651 if (gret
!= GSS_S_COMPLETE
)
652 gss_log(3, "failed gss_release_name: %s",
653 gss_error_tostring(gret
, minor
, buf
,
666 return (ISC_R_NOTIMPLEMENTED
);
671 dst_gssapi_deletectx(isc_mem_t
*mctx
, gss_ctx_id_t
*gssctx
)
674 OM_uint32 gret
, minor
;
679 REQUIRE(gssctx
!= NULL
&& *gssctx
!= NULL
);
681 /* Delete the context from the GSS provider */
682 gret
= gss_delete_sec_context(&minor
, gssctx
, GSS_C_NO_BUFFER
);
683 if (gret
!= GSS_S_COMPLETE
) {
684 /* Log the error, but still free the context's memory */
685 gss_log(3, "Failure deleting security context %s",
686 gss_error_tostring(gret
, minor
, buf
, sizeof(buf
)));
688 return(ISC_R_SUCCESS
);
692 return (ISC_R_NOTIMPLEMENTED
);
697 gss_error_tostring(isc_uint32_t major
, isc_uint32_t minor
,
698 char *buf
, size_t buflen
) {
700 gss_buffer_desc msg_minor
= GSS_C_EMPTY_BUFFER
,
701 msg_major
= GSS_C_EMPTY_BUFFER
;
702 OM_uint32 msg_ctx
, minor_stat
;
704 /* Handle major status */
706 (void)gss_display_status(&minor_stat
, major
, GSS_C_GSS_CODE
,
707 GSS_C_NULL_OID
, &msg_ctx
, &msg_major
);
709 /* Handle minor status */
711 (void)gss_display_status(&minor_stat
, minor
, GSS_C_MECH_CODE
,
712 GSS_C_NULL_OID
, &msg_ctx
, &msg_minor
);
714 snprintf(buf
, buflen
, "GSSAPI error: Major = %s, Minor = %s.",
715 (char *)msg_major
.value
, (char *)msg_minor
.value
);
717 if (msg_major
.length
!= 0)
718 (void)gss_release_buffer(&minor_stat
, &msg_major
);
719 if (msg_minor
.length
!= 0)
720 (void)gss_release_buffer(&minor_stat
, &msg_minor
);
723 snprintf(buf
, buflen
, "GSSAPI error: Major = %u, Minor = %u.",
731 gss_log(int level
, const char *fmt
, ...) {
735 isc_log_vwrite(dns_lctx
, DNS_LOGCATEGORY_GENERAL
,
736 DNS_LOGMODULE_TKEY
, ISC_LOG_DEBUG(level
), fmt
, ap
);