1/*
2 * Copyright (c) 1997 - 2005 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Portions Copyright (c) 2009 - 2010 Apple Inc. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * 3. Neither the name of the Institute nor the names of its contributors
20 *    may be used to endorse or promote products derived from this software
21 *    without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36#include "gsskrb5_locl.h"
37#include <heim_threads.h>
38#include <gssapi_spi.h>
39#include <pkinit_asn1.h>
40#include <hex.h>
41
42OM_uint32
43__gsskrb5_ccache_lifetime(OM_uint32 *minor_status,
44			  krb5_context context,
45			  krb5_ccache id,
46			  krb5_principal principal,
47			  time_t *endtime)
48{
49    krb5_creds in_cred, out_cred;
50    krb5_const_realm realm;
51    krb5_error_code kret;
52
53    memset(&in_cred, 0, sizeof(in_cred));
54    in_cred.client = principal;
55
56    realm = krb5_principal_get_realm(context,  principal);
57    if (realm == NULL) {
58	_gsskrb5_clear_status ();
59	*minor_status = KRB5_PRINC_NOMATCH; /* XXX */
60	return GSS_S_FAILURE;
61    }
62
63    kret = krb5_make_principal(context, &in_cred.server,
64			       realm, KRB5_TGS_NAME, realm, NULL);
65    if (kret) {
66	*minor_status = kret;
67	return GSS_S_FAILURE;
68    }
69
70    kret = krb5_cc_retrieve_cred(context, id, 0, &in_cred, &out_cred);
71    krb5_free_principal(context, in_cred.server);
72    if (kret) {
73	*minor_status = 0;
74	*endtime = 0;
75	return GSS_S_COMPLETE;
76    }
77
78    *endtime = out_cred.times.endtime;
79    krb5_free_cred_contents(context, &out_cred);
80
81    return GSS_S_COMPLETE;
82}
83
84/*
85 * Check if there is at least one entry in the keytab before
86 * declaring it as an useful keytab.
87 */
88
89static int
90check_keytab(krb5_context context,
91	     gsskrb5_cred handle,
92	     const char *service,
93	     int require_lkdc)
94{
95    krb5_keytab_entry tmp;
96    krb5_error_code ret;
97    krb5_kt_cursor c;
98    int found = 0;
99
100    ret = krb5_kt_start_seq_get (context, handle->keytab, &c);
101    if (ret)
102	return 0;
103    while (!found && krb5_kt_next_entry(context, handle->keytab, &tmp, &c) == 0) {
104	krb5_principal principal = tmp.principal;
105
106	if (service) {
107	    if (principal->name.name_string.len < 1
108		|| strcmp(principal->name.name_string.val[0], service) != 0)
109		goto next;
110	}
111	if (require_lkdc) {
112	    if (krb5_principal_is_lkdc(context, principal))
113		found = 1;
114	    if (krb5_principal_is_pku2u(context, principal))
115		found = 1;
116	} else
117	    found = 1;
118      next:
119	krb5_kt_free_entry(context, &tmp);
120    }
121    krb5_kt_end_seq_get (context, handle->keytab, &c);
122
123    return found;
124}
125
126/*
127 *
128 */
129
130static krb5_error_code
131get_keytab(krb5_context context, gsskrb5_cred handle, int require_lkdc)
132{
133    krb5_error_code kret;
134
135    HEIMDAL_MUTEX_lock(&gssapi_keytab_mutex);
136
137    if (_gsskrb5_keytab != NULL) {
138	char *name = NULL;
139
140	kret = krb5_kt_get_full_name(context, _gsskrb5_keytab, &name);
141	if (kret == 0) {
142	    kret = krb5_kt_resolve(context, name, &handle->keytab);
143	    krb5_xfree(name);
144	}
145    } else
146	kret = krb5_kt_default(context, &handle->keytab);
147
148    if (kret)
149	goto out;
150
151    /*
152     * If caller requested, check that we have the user in the keytab.
153     */
154
155    if (handle->principal) {
156	krb5_keytab_entry entry;
157
158	if (krb5_principal_is_gss_hostbased_service(context, handle->principal)) {
159	    /*
160	     * check if we have a service in the keytab
161	     */
162	    const char *service = handle->principal->name.name_string.val[0];
163
164	    if (!check_keytab(context, handle, service, require_lkdc)) {
165		kret = KRB5_KT_NOTFOUND;
166		krb5_set_error_message(context, kret,
167				       "Didn't find service %s in keytab", service);
168		goto out;
169	    }
170	} else {
171	    kret = krb5_kt_get_entry(context, handle->keytab, handle->principal,
172				     0, 0, &entry);
173	    if (kret)
174		goto out;
175
176	    /*
177	     * Update the name with the entry from the keytab in case we
178	     * have a gss hostname service name principal
179	     */
180	    krb5_free_principal(context, handle->principal);
181	    kret = krb5_copy_principal(context, entry.principal, &handle->principal);
182	    krb5_kt_free_entry(context, &entry);
183	    if (kret)
184		goto out;
185	}
186
187    } else {
188	if (!check_keytab(context, handle, NULL, require_lkdc)) {
189	    kret = KRB5_KT_NOTFOUND;
190	    goto out;
191	}
192    }
193
194 out:
195    if (kret && handle->keytab) {
196	krb5_kt_close(context, handle->keytab);
197	handle->keytab = NULL;
198    }
199
200    HEIMDAL_MUTEX_unlock(&gssapi_keytab_mutex);
201
202    return (kret);
203}
204
205static OM_uint32 acquire_initiator_cred
206		  (OM_uint32 * minor_status,
207		   krb5_context context,
208		   const gss_name_t desired_name,
209		   OM_uint32 time_req,
210		   gss_cred_usage_t cred_usage,
211		   gsskrb5_cred handle
212		  )
213{
214    OM_uint32 ret = GSS_S_FAILURE;
215    krb5_creds cred;
216    krb5_principal def_princ = NULL;
217    krb5_get_init_creds_opt *opt;
218    krb5_ccache ccache = NULL;
219    krb5_error_code kret;
220
221    memset(&cred, 0, sizeof(cred));
222
223    /*
224     * If we have a preferred principal, lets try to find it in all
225     * caches, otherwise, fall back to default cache, ignore all
226     * errors while searching.
227     */
228
229    if (handle->principal) {
230	kret = krb5_cc_cache_match (context,
231				    handle->principal,
232				    &ccache);
233	if (kret == 0) {
234	    goto found;
235	}
236    }
237
238    if (ccache == NULL) {
239	kret = krb5_cc_default(context, &ccache);
240	if (kret)
241	    goto end;
242    }
243    kret = krb5_cc_get_principal(context, ccache, &def_princ);
244    if (kret != 0) {
245	/* we'll try to use a keytab below */
246	krb5_cc_close(context, ccache);
247	def_princ = NULL;
248	kret = 0;
249    } else if (handle->principal == NULL)  {
250	kret = krb5_copy_principal(context, def_princ, &handle->principal);
251	if (kret)
252	    goto end;
253    } else if (handle->principal != NULL &&
254	       krb5_principal_compare(context, handle->principal,
255				      def_princ) == FALSE) {
256	krb5_free_principal(context, def_princ);
257	def_princ = NULL;
258	krb5_cc_close(context, ccache);
259	ccache = NULL;
260    }
261    if (def_princ == NULL) {
262	/* We have no existing credentials cache,
263	 * so attempt to get a TGT using a keytab.
264	 */
265	if (handle->principal == NULL) {
266	    kret = krb5_get_default_principal(context, &handle->principal);
267	    if (kret)
268		goto end;
269	}
270	/*
271	 * Require user is in the keytab before trying to talk to
272	 * the KDC.
273	 */
274	kret = get_keytab(context, handle, 0);
275	if (kret)
276	    goto end;
277	/* since the name might have changed, let double check the credential cache */
278	kret = krb5_cc_cache_match(context, handle->principal, &ccache);
279	if (kret == 0)
280	    goto found;
281	kret = krb5_get_init_creds_opt_alloc(context, &opt);
282	if (kret)
283	    goto end;
284	kret = krb5_get_init_creds_keytab(context, &cred,
285					  handle->principal, handle->keytab,
286					  0, NULL, opt);
287	krb5_get_init_creds_opt_free(context, opt);
288	if (kret)
289	    goto end;
290	kret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, &ccache);
291	if (kret)
292	    goto end;
293	kret = krb5_cc_initialize(context, ccache, cred.client);
294	if (kret) {
295	    krb5_cc_destroy(context, ccache);
296	    goto end;
297	}
298	kret = krb5_cc_store_cred(context, ccache, &cred);
299	if (kret) {
300	    krb5_cc_destroy(context, ccache);
301	    goto end;
302	}
303	handle->endtime = cred.times.endtime;
304	handle->cred_flags |= GSS_CF_DESTROY_CRED_ON_RELEASE;
305
306    } else {
307    found:
308	ret = __gsskrb5_ccache_lifetime(minor_status,
309					context,
310					ccache,
311					handle->principal,
312					&handle->endtime);
313	if (ret != GSS_S_COMPLETE) {
314	    krb5_cc_close(context, ccache);
315	    goto end;
316	}
317	kret = 0;
318    }
319
320    handle->ccache = ccache;
321    ret = GSS_S_COMPLETE;
322
323end:
324    if (cred.client != NULL)
325	krb5_free_cred_contents(context, &cred);
326    if (def_princ != NULL)
327	krb5_free_principal(context, def_princ);
328    if (ret != GSS_S_COMPLETE && kret != 0)
329	*minor_status = kret;
330    return (ret);
331}
332
333static OM_uint32 acquire_acceptor_cred
334		  (OM_uint32 * minor_status,
335		   krb5_context context,
336		   const gss_name_t desired_name,
337		   OM_uint32 time_req,
338		   gss_cred_usage_t cred_usage,
339		   gsskrb5_cred handle
340		  )
341{
342    krb5_error_code kret;
343
344    kret = get_keytab(context, handle, 0);
345
346    if (kret) {
347	if (handle->keytab != NULL) {
348	    krb5_kt_close(context, handle->keytab);
349	    handle->keytab = NULL;
350	}
351	*minor_status = kret;
352	return GSS_S_FAILURE;
353    }
354
355    handle->endtime = INT_MAX;
356
357    return GSS_S_COMPLETE;
358}
359
360static OM_uint32
361_acquire_uuid_name(OM_uint32 *minor_status,
362		   krb5_context context,
363		   krb5_const_principal princ,
364		   int *iakerb,
365		   gsskrb5_cred handle)
366{
367    krb5_error_code ret;
368    krb5_uuid uuid;
369
370    *iakerb = 0;
371
372    if (princ->name.name_type != KRB5_NT_CACHE_UUID)
373	return GSS_S_BAD_NAMETYPE;
374
375    if (princ->name.name_string.len != 1 || strcmp(princ->realm, "UUID") != 0)
376	return GSS_S_BAD_NAME;
377
378    if (krb5_string_to_uuid(princ->name.name_string.val[0], uuid))
379	return GSS_S_BAD_NAME;
380
381    ret = krb5_cc_resolve_by_uuid(context, NULL,
382				  &handle->ccache, uuid);
383    if (ret) {
384	*minor_status = ret;
385	return GSS_S_FAILURE;
386    }
387
388    ret = krb5_cc_get_principal(context, handle->ccache, &handle->principal);
389    if (ret) {
390	*minor_status = ret;
391	return GSS_S_FAILURE;
392    }
393
394    {
395	krb5_data data;
396
397	ret = krb5_cc_get_config(context, handle->ccache, NULL, "iakerb", &data);
398	if (ret == 0) {
399	    *iakerb = 1;
400	    handle->endtime = INT_MAX;
401	    krb5_data_free(&data);
402	    return 0;
403	}
404    }
405
406    return __gsskrb5_ccache_lifetime(minor_status,
407				     context,
408				     handle->ccache,
409				     handle->principal,
410				     &handle->endtime);
411}
412
413
414OM_uint32 GSSAPI_CALLCONV
415_gsskrb5_acquire_cred(OM_uint32 * minor_status,
416		      const gss_name_t desired_name,
417		      OM_uint32 time_req,
418		      const gss_OID_set desired_mechs,
419		      gss_cred_usage_t cred_usage,
420		      gss_cred_id_t * output_cred_handle,
421		      gss_OID_set * actual_mechs,
422		      OM_uint32 * time_rec)
423{
424    krb5_const_principal principal = (krb5_const_principal)desired_name;
425    krb5_context context;
426    gsskrb5_cred handle;
427    OM_uint32 ret, junk;
428
429    cred_usage &= GSS_C_OPTION_MASK;
430
431    if (cred_usage != GSS_C_ACCEPT && cred_usage != GSS_C_INITIATE && cred_usage != GSS_C_BOTH) {
432	*minor_status = GSS_KRB5_S_G_BAD_USAGE;
433	return GSS_S_FAILURE;
434    }
435
436    GSSAPI_KRB5_INIT(&context);
437
438    *output_cred_handle = NULL;
439
440    handle = calloc(1, sizeof(*handle));
441    if (handle == NULL) {
442	*minor_status = ENOMEM;
443        return (GSS_S_FAILURE);
444    }
445
446    HEIMDAL_MUTEX_init(&handle->cred_id_mutex);
447
448    if (principal && principal->name.name_type == KRB5_NT_CACHE_UUID) {
449	int iakerb = 0;
450
451	ret = _acquire_uuid_name(minor_status, context, principal, &iakerb, handle);
452	if (iakerb) {
453	    *minor_status = 0;
454	    ret = GSS_S_BAD_NAME;
455	}
456	if (ret) {
457	    _gsskrb5_release_cred(&junk, (gss_cred_id_t *)&handle);
458	    return ret;
459	}
460	goto out;
461    }
462
463    if (principal) {
464	krb5_error_code kret;
465
466	kret = krb5_copy_principal(context, principal, &handle->principal);
467	if (kret) {
468	    _gsskrb5_release_cred(&junk, (gss_cred_id_t *)&handle);
469	    *minor_status = kret;
470	    return GSS_S_FAILURE;
471	}
472    }
473    if (cred_usage == GSS_C_INITIATE || cred_usage == GSS_C_BOTH) {
474	ret = acquire_initiator_cred(minor_status, context,
475				     desired_name, time_req,
476				     cred_usage, handle);
477    	if (ret != GSS_S_COMPLETE) {
478	    HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
479	    krb5_free_principal(context, handle->principal);
480	    free(handle);
481	    return (ret);
482	}
483    }
484    if (cred_usage == GSS_C_ACCEPT || cred_usage == GSS_C_BOTH) {
485	ret = acquire_acceptor_cred(minor_status, context,
486				    desired_name, time_req,
487				    cred_usage, handle);
488	if (ret != GSS_S_COMPLETE) {
489	    HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
490	    krb5_free_principal(context, handle->principal);
491	    free(handle);
492	    return (ret);
493	}
494    }
495
496 out:
497
498    handle->usage = cred_usage;
499    *minor_status = 0;
500    *output_cred_handle = (gss_cred_id_t)handle;
501
502    ret = _gsskrb5_inquire_cred(minor_status, *output_cred_handle,
503				NULL, time_rec, NULL, actual_mechs);
504    if (ret) {
505	_gsskrb5_release_cred(&junk, output_cred_handle);
506	return ret;
507    }
508
509    return (GSS_S_COMPLETE);
510}
511
512OM_uint32
513_gssiakerb_acquire_cred(OM_uint32 * minor_status,
514			const gss_name_t desired_name,
515			OM_uint32 time_req,
516			const gss_OID_set desired_mechs,
517			gss_cred_usage_t cred_usage,
518			gss_cred_id_t * output_cred_handle,
519			gss_OID_set * actual_mechs,
520			OM_uint32 * time_rec)
521{
522    krb5_principal princ = (krb5_principal)desired_name;
523    OM_uint32 major_status, junk;
524    krb5_context context;
525    krb5_error_code ret;
526    gsskrb5_cred handle;
527    krb5_data data;
528    int iakerb = 0;
529
530    GSSAPI_KRB5_INIT(&context);
531
532    *minor_status = 0;
533    *output_cred_handle = NULL;
534
535    if (cred_usage != GSS_C_INITIATE && cred_usage != GSS_C_BOTH)
536	return GSS_S_FAILURE;
537    if (princ == NULL)
538	return GSS_S_FAILURE;
539
540    handle = calloc(1, sizeof(*handle));
541    if (handle == NULL)
542        return GSS_S_FAILURE;
543
544    HEIMDAL_MUTEX_init(&handle->cred_id_mutex);
545
546    major_status = _acquire_uuid_name(minor_status, context, princ, &iakerb, handle);
547    if (major_status)
548	return major_status;
549    if (!iakerb)
550	return GSS_S_BAD_NAME;
551
552    if ((ret = krb5_cc_get_config(context, handle->ccache, NULL, "password", &data)) == 0) {
553
554	ret = asprintf(&handle->password, "%.*s", (int)data.length, (char *)data.data);
555	memset(data.data, 0, data.length);
556	krb5_data_free(&data);
557	if (ret <= 0 || handle->password == NULL) {
558	    _gsskrb5_release_cred(&junk, (gss_cred_id_t *)&handle);
559	    *minor_status = ENOMEM;
560	    return GSS_S_FAILURE;
561	}
562
563#ifdef PKINIT
564    } else if ((ret = krb5_cc_get_config(context, handle->ccache, NULL, "certificate-ref", &data)) == 0) {
565	hx509_certs certs;
566	hx509_query *q;
567
568	ret = hx509_certs_init(context->hx509ctx, "KEYCHAIN:", 0, NULL, &certs);
569	if (ret) {
570	    krb5_data_free(&data);
571	    hx509_certs_free(&certs);
572	    _gsskrb5_release_cred(&junk, (gss_cred_id_t *)&handle);
573	    *minor_status = ret;
574	    return GSS_S_FAILURE;
575	}
576
577	ret = hx509_query_alloc(context->hx509ctx, &q);
578	if (ret) {
579	    krb5_data_free(&data);
580	    hx509_certs_free(&certs);
581	    _gsskrb5_release_cred(&junk, (gss_cred_id_t *)&handle);
582	    *minor_status = ret;
583	    return GSS_S_FAILURE;
584	}
585
586	hx509_query_match_option(q, HX509_QUERY_OPTION_PRIVATE_KEY);
587	hx509_query_match_option(q, HX509_QUERY_OPTION_KU_DIGITALSIGNATURE);
588	hx509_query_match_persistent(q, &data);
589
590	ret = _krb5_pk_find_cert(context, 1, certs, q, &handle->cert);
591	krb5_data_free(&data);
592	hx509_certs_free(&certs);
593	hx509_query_free(context->hx509ctx, q);
594	if (ret != 0) {
595	    _gss_mg_log(1, "gss-krb5: failed to find certificate ref %d", ret);
596	    _gsskrb5_release_cred(&junk, (gss_cred_id_t *)&handle);
597	    *minor_status = ret;
598	    return GSS_S_FAILURE;
599	}
600#endif
601    } else if ((ret = krb5_cc_get_config(context, handle->ccache, NULL, "iakerb", &data)) == 0) {
602	handle->cred_flags |= GSS_CF_IAKERB_RESOLVED;
603	krb5_data_free(&data);
604    } else {
605	_gsskrb5_release_cred(&junk, (gss_cred_id_t *)&handle);
606	*minor_status = 0;
607	return GSS_S_FAILURE;
608    }
609
610    handle->usage = GSS_C_INITIATE;
611    handle->endtime = INT_MAX;
612
613    *output_cred_handle = (gss_cred_id_t)handle;
614    *minor_status = 0;
615    return GSS_S_COMPLETE;
616}
617
618
619OM_uint32
620_gss_iakerb_acquire_cred_ext(OM_uint32 * minor_status,
621			     const gss_name_t desired_name,
622			     gss_const_OID credential_type,
623			     const void *credential_data,
624			     OM_uint32 time_req,
625			     gss_const_OID desired_mech,
626			     gss_cred_usage_t cred_usage,
627			     gss_cred_id_t * output_cred_handle)
628{
629    krb5_context context;
630    gsskrb5_cred handle;
631    krb5_error_code ret;
632    krb5_creds cred;
633    gss_buffer_t credential_buffer = NULL;
634#ifdef PKINIT
635    hx509_cert cert = NULL;
636#endif
637
638    memset(&cred, 0, sizeof(cred));
639
640    if (cred_usage != GSS_C_INITIATE && cred_usage != GSS_C_BOTH)
641	return GSS_S_FAILURE;
642
643    GSSAPI_KRB5_INIT_STATUS(&context, status);
644
645    /* pick up the credential */
646
647    if (gss_oid_equal(credential_type, GSS_C_CRED_PASSWORD)) {
648
649	credential_buffer = (gss_buffer_t)credential_data;
650
651	if (credential_buffer->length + 1 < credential_buffer->length)
652	    return GSS_S_FAILURE;
653
654#ifdef PKINIT
655    } else if (gss_oid_equal(credential_type, GSS_C_CRED_CERTIFICATE)) {
656
657	cert = (hx509_cert)credential_data;
658
659    } else if (gss_oid_equal(credential_type, GSS_C_CRED_SecIdentity)) {
660
661	ret = hx509_cert_init_SecFramework(context->hx509ctx, rk_UNCONST(credential_data), &cert);
662	if (ret) {
663	    *minor_status = ret;
664	    return GSS_S_FAILURE;
665	}
666#endif
667    } else {
668	*minor_status = KRB5_NOCREDS_SUPPLIED;
669	return GSS_S_FAILURE;
670    }
671
672
673    if (desired_name == GSS_C_NO_NAME)
674	return GSS_S_FAILURE;
675
676    handle = calloc(1, sizeof(*handle));
677    if (handle == NULL)
678        return (GSS_S_FAILURE);
679
680    HEIMDAL_MUTEX_init(&handle->cred_id_mutex);
681
682    handle->usage = GSS_C_INITIATE;
683
684    {
685	krb5_principal princ = (krb5_principal)desired_name;
686
687	ret = krb5_copy_principal(context, princ, &handle->principal);
688	if (ret) {
689	    HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
690	    free(handle);
691	    *minor_status = ret;
692	    return GSS_S_FAILURE;
693	}
694    }
695
696    if (credential_buffer) {
697
698	handle->password = malloc(credential_buffer->length + 1);
699	if (handle->password == NULL) {
700	    krb5_free_principal(context, handle->principal);
701	    HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
702	    free(handle);
703	    *minor_status = ENOMEM;
704	    return GSS_S_FAILURE;
705	}
706
707	memcpy(handle->password, credential_buffer->value, credential_buffer->length);
708	handle->password[credential_buffer->length] = '\0';
709    }
710#ifdef PKINIT
711    if (cert)
712	handle->cert = heim_retain(cert);
713#endif
714
715    handle->keytab = NULL;
716    handle->ccache = NULL;
717    handle->endtime = INT_MAX;
718
719    /*
720     * Lets overwrite the same credentials if we already have it
721     */
722    ret = krb5_cc_cache_match(context, handle->principal, &handle->ccache);
723    if (ret) {
724	ret = krb5_cc_new_unique(context, krb5_cc_type_api, NULL, &handle->ccache);
725	if (ret)
726	    goto out;
727    }
728
729    ret = krb5_cc_initialize(context, handle->ccache, handle->principal);
730    if (ret)
731	goto out;
732
733    {
734	krb5_data data;
735	krb5_data_zero(&data);
736	krb5_cc_set_config(context, handle->ccache, NULL, "iakerb", &data);
737    }
738
739    if (handle->password) {
740	krb5_data pw;
741	pw.data = handle->password;
742	pw.length = strlen(handle->password);
743	ret = krb5_cc_set_config(context, handle->ccache, NULL, "password", &pw);
744	if (ret)
745	    goto out;
746    }
747#ifdef PKINIT
748    if (handle->cert) {
749	krb5_data pd;
750	ret = hx509_cert_get_persistent(handle->cert, &pd);
751	if (ret)
752	    goto out;
753	ret = krb5_cc_set_config(context, handle->ccache, NULL, "certificate-ref", &pd);
754	der_free_octet_string(&pd);
755	if (ret)
756	    goto out;
757    }
758#endif
759
760    *output_cred_handle = (gss_cred_id_t) handle;
761
762    *minor_status = 0;
763
764    return GSS_S_COMPLETE;
765
766 out:
767
768    krb5_free_principal(context, handle->principal);
769    if (handle->password) {
770	memset(handle->password, 0, strlen(handle->password));
771	free(handle->password);
772    }
773#ifdef PKINIT
774    if (handle->cert)
775	hx509_cert_free(handle->cert);
776#endif
777    if (handle->ccache)
778	krb5_cc_destroy(context, handle->ccache);
779    HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
780    free(handle);
781    *minor_status = ret;
782    return GSS_S_FAILURE;
783}
784
785/*
786 *
787 */
788
789OM_uint32 GSSAPI_CALLCONV
790_gss_krb5_acquire_cred_ext(OM_uint32 * minor_status,
791			   const gss_name_t desired_name,
792			   gss_const_OID credential_type,
793			   const void *credential_data,
794			   OM_uint32 time_req,
795			   gss_const_OID desired_mech,
796			   gss_cred_usage_t cred_usage,
797			   gss_cred_id_t * output_cred_handle)
798{
799    krb5_init_creds_context ctx = NULL;
800    krb5_get_init_creds_opt *opt = NULL;
801    krb5_principal principal;
802    krb5_context context;
803    krb5_error_code kret;
804    gsskrb5_cred handle = NULL;
805    krb5_ccache ccache = NULL, ccachereplace = NULL;
806    char *passwordstr = NULL;
807    char *cache_name = NULL;
808    char *lkdc_hostname = NULL;
809    hx509_cert hxcert = NULL;
810    heim_array_t bundleacl = NULL;
811    krb5_principal new_name = NULL;
812
813    GSSAPI_KRB5_INIT(&context);
814
815    cred_usage &= GSS_C_OPTION_MASK;
816
817    if (cred_usage != GSS_C_INITIATE && cred_usage != GSS_C_BOTH) {
818	*minor_status = GSS_KRB5_S_G_BAD_USAGE;
819	return GSS_S_FAILURE;
820    }
821
822    if (desired_name == GSS_C_NO_NAME)
823	return GSS_S_FAILURE;
824
825    if (gss_oid_equal(credential_type, GSS_C_CRED_HEIMBASE)) {
826	heim_object_t pw, cname, cert, lkdc;
827	heim_dict_t dict = (heim_dict_t)credential_data;
828
829	pw = heim_dict_copy_value(dict, _gsskrb5_kGSSICPassword);
830	if (pw) {
831	    if (heim_get_tid(pw) == heim_string_get_type_id()) {
832		passwordstr = heim_string_copy_utf8(pw);
833		if (passwordstr == NULL) {
834		    kret = ENOMEM;
835		    goto out;
836		}
837	    } else if (heim_get_tid(pw) == heim_data_get_type_id()) {
838		passwordstr = malloc(heim_data_get_length(pw) + 1);
839		if (passwordstr == NULL) {
840		    kret = ENOMEM;
841		    goto out;
842		}
843		memcpy(passwordstr, heim_data_get_bytes(pw), heim_data_get_length(pw));
844		passwordstr[heim_data_get_length(pw)] = '\0';
845	    }
846	    heim_release(pw);
847	}
848
849	cname = heim_dict_copy_value(dict, _gsskrb5_kGSSICKerberosCacheName);
850	if (cname) {
851	    cache_name = heim_string_copy_utf8(cname);
852	    heim_release(cname);
853	}
854
855	bundleacl = heim_dict_copy_value(dict, _gsskrb5_kGSSICAppIdentifierACL);
856
857#ifdef PKINIT
858	cert = heim_dict_copy_value(dict, _gsskrb5_kGSSICCertificate);
859	if (cert) {
860	    kret = hx509_cert_init_SecFramework(context->hx509ctx, cert, &hxcert);
861	    if (kret)
862		goto out;
863	    heim_release(cert);
864	}
865#endif
866
867	lkdc = heim_dict_copy_value(dict, _gsskrb5_kGSSICLKDCHostname);
868	if (lkdc) {
869	    lkdc_hostname = heim_string_copy_utf8(lkdc);
870	    heim_release(lkdc);
871	}
872
873    } else if (gss_oid_equal(credential_type, GSS_C_CRED_PASSWORD)) {
874	gss_buffer_t password = (gss_buffer_t)credential_data;
875
876	passwordstr = malloc(password->length + 1);
877	if (passwordstr == NULL) {
878	    kret = ENOMEM;
879	    goto out;
880	}
881
882	memcpy(passwordstr, password->value, password->length);
883	passwordstr[password->length] = '\0';
884
885    } else {
886	*minor_status = KRB5_NOCREDS_SUPPLIED; /* XXX */
887	return GSS_S_FAILURE;
888    }
889
890    if (passwordstr == NULL && hxcert == NULL) {
891	*minor_status = KRB5_NOCREDS_SUPPLIED; /* XXX */
892	return GSS_S_FAILURE;
893    }
894
895    *output_cred_handle = NULL;
896
897    handle = calloc(1, sizeof(*handle));
898    if (handle == NULL) {
899	kret = krb5_enomem(context);
900        goto out;
901    }
902
903    principal = (krb5_principal)desired_name;
904
905    HEIMDAL_MUTEX_init(&handle->cred_id_mutex);
906
907    kret = krb5_copy_principal(context, principal, &handle->principal);
908    if (kret)
909	goto out;
910
911    kret = krb5_cc_new_unique(context, cache_name, NULL, &ccache);
912    if (kret)
913	goto out;
914
915    kret = krb5_get_init_creds_opt_alloc(context, &opt);
916    if (kret)
917	goto out;
918
919    krb5_get_init_creds_opt_set_default_flags(context, "gss", krb5_principal_get_realm(context, principal), opt);
920
921    krb5_get_init_creds_opt_set_forwardable(opt, 1);
922    krb5_get_init_creds_opt_set_proxiable(opt, 1);
923    krb5_get_init_creds_opt_set_renew_life(opt, 3600 * 24 * 30); /* 1 month */
924    krb5_get_init_creds_opt_set_canonicalize(context, opt, TRUE);
925    krb5_get_init_creds_opt_set_win2k(context, opt, TRUE);
926
927    if (hxcert) {
928	char *cert_pool[2] = { "KEYCHAIN:", NULL };
929	kret = krb5_get_init_creds_opt_set_pkinit(context, opt, principal,
930						 NULL, "KEYCHAIN:",
931						 cert_pool, NULL, 8,
932						 NULL, NULL, NULL);
933	if (kret)
934	    goto out;
935    }
936
937    kret = krb5_init_creds_init(context, handle->principal, NULL, NULL, NULL, opt, &ctx);
938    if (kret)
939	goto out;
940
941    if (passwordstr) {
942	kret = krb5_init_creds_set_password(context, ctx, passwordstr);
943
944	memset(passwordstr, 0, strlen(passwordstr));
945	free(passwordstr);
946	passwordstr = NULL;
947
948	if (kret)
949	    goto out;
950    }
951
952    if (hxcert) {
953	kret = krb5_init_creds_set_pkinit_client_cert(context, ctx, hxcert);
954	if (kret)
955	    goto out;
956    }
957
958    if (lkdc_hostname) {
959	kret = krb5_init_creds_set_kdc_hostname(context, ctx, lkdc_hostname);
960	free(lkdc_hostname);
961	lkdc_hostname = NULL;
962	if (kret)
963	    goto out;
964    }
965
966    kret = krb5_init_creds_get(context, ctx);
967    if (kret)
968	goto out;
969
970    handle->endtime = _krb5_init_creds_get_cred_endtime(context, ctx);
971
972    /*
973     * If we where subjected to a referral, update the name of the credential
974     */
975    new_name = _krb5_init_creds_get_cred_client(context, ctx);
976    if (new_name && !krb5_principal_compare(context, new_name, handle->principal)) {
977	krb5_free_principal(context, handle->principal);
978	kret = krb5_copy_principal(context, new_name, &handle->principal);
979	if (kret)
980	    goto out;
981    }
982
983    /*
984     * Now store the credential
985     */
986
987    if (cache_name) {
988	/* check if caller told us to use a specific cache */
989	kret = krb5_cc_resolve(context, cache_name, &ccachereplace);
990	if (kret)
991	    goto out;
992
993    } else {
994	/*
995	 * check if there an existing cache to overwrite before we lay
996	 * down the new cache
997	 */
998	(void)krb5_cc_cache_match(context, principal, &ccachereplace);
999    }
1000
1001
1002    kret = krb5_init_creds_store(context, ctx, ccache);
1003    if (kret == 0)
1004	kret = krb5_init_creds_store_config(context, ctx, ccache);
1005
1006    if (bundleacl)
1007	krb5_cc_set_acl(context, ccache, "kHEIMAttrBundleIdentifierACL", bundleacl);
1008
1009    krb5_init_creds_free(context, ctx);
1010    ctx = NULL;
1011    if (kret)
1012	goto out;
1013
1014    krb5_get_init_creds_opt_free(context, opt);
1015    opt = NULL;
1016
1017    /*
1018     * If we have a credential with the same naame, lets overwrite it
1019     */
1020
1021    if (ccachereplace) {
1022	kret = krb5_cc_move(context, ccache, ccachereplace);
1023	if (kret)
1024	    goto out;
1025	handle->ccache = ccachereplace;
1026	ccachereplace = NULL;
1027    } else {
1028	handle->ccache = ccache;
1029    }
1030
1031    handle->usage = cred_usage;
1032    *minor_status = 0;
1033    *output_cred_handle = (gss_cred_id_t)handle;
1034
1035    if (cache_name)
1036	free(cache_name);
1037
1038    heim_release(bundleacl);
1039
1040    return GSS_S_COMPLETE;
1041
1042 out:
1043    if (bundleacl)
1044	heim_release(bundleacl);
1045    if (opt)
1046	krb5_get_init_creds_opt_free(context, opt);
1047    if (ctx)
1048	krb5_init_creds_free(context, ctx);
1049    if (lkdc_hostname)
1050	free(lkdc_hostname);
1051    if (cache_name)
1052	free(cache_name);
1053    if (passwordstr) {
1054	memset(passwordstr, 0, strlen(passwordstr));
1055	free(passwordstr);
1056    }
1057    if (ccachereplace)
1058	krb5_cc_close(context, ccachereplace);
1059    if (ccache)
1060	krb5_cc_destroy(context, ccache);
1061    if (handle) {
1062	if (handle->principal)
1063	    krb5_free_principal(context, handle->principal);
1064
1065	HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
1066	free(handle);
1067    }
1068
1069    *minor_status = kret;
1070    return GSS_S_FAILURE;
1071}
1072
1073
1074#ifdef PKINIT
1075
1076krb5_error_code
1077_gsspku2u_principal(krb5_context context,
1078		    struct hx509_cert_data *cert,
1079		    krb5_principal *principal)
1080{
1081    hx509_octet_string_list list;
1082    krb5_error_code ret;
1083    int found = 0;
1084    unsigned i;
1085    char *name;
1086
1087    *principal = NULL;
1088
1089    /*
1090     * First try to map PKINIT SAN to a Kerberos principal
1091     */
1092
1093    ret = hx509_cert_find_subjectAltName_otherName(context->hx509ctx, cert,
1094						   &asn1_oid_id_pkinit_san,
1095						   &list);
1096    if (ret == 0) {
1097	for (i = 0; !found && i < list.len; i++) {
1098	    KRB5PrincipalName r;
1099
1100	    ret = decode_KRB5PrincipalName(list.val[i].data,
1101					   list.val[i].length,
1102					   &r, NULL);
1103	    if (ret)
1104		continue;
1105
1106	    ret = _krb5_principalname2krb5_principal(context, principal,
1107						     r.principalName,
1108						     KRB5_PKU2U_REALM_NAME);
1109	    free_KRB5PrincipalName(&r);
1110	    if (ret == 0)
1111		found = 1;
1112	}
1113	hx509_free_octet_string_list(&list);
1114    }
1115    if (found)
1116	return 0;
1117
1118    /*
1119     *
1120     */
1121
1122    ret = hx509_cert_get_appleid(context->hx509ctx, cert, &name);
1123    if (ret == 0) {
1124	ret = krb5_make_principal(context, principal,
1125				  KRB5_PKU2U_REALM_NAME,
1126				  name, NULL);
1127	hx509_xfree(name);
1128	if (ret == 0) {
1129	    (*principal)->name.name_type = KRB5_NT_ENTERPRISE_PRINCIPAL;
1130	    return 0;
1131	}
1132    }
1133
1134    /*
1135     * Give up and just WELLKNOWN and assertion instead
1136     */
1137
1138    ret = krb5_make_principal(context, principal, KRB5_PKU2U_REALM_NAME,
1139			      KRB5_WELLKNOWN_NAME, KRB5_NULL_NAME, NULL);
1140    if (ret == 0)
1141	(*principal)->name.name_type = KRB5_NT_WELLKNOWN;
1142    return ret;
1143}
1144
1145
1146
1147
1148struct search {
1149    krb5_context context;
1150    krb5_principal principal;
1151};
1152
1153static int
1154match_pkinit_san(hx509_context context, hx509_cert cert, void *ctx)
1155{
1156    struct search *s = ctx;
1157    return _krb5_pk_match_cert(s->context, s->principal, cert, 0);
1158}
1159
1160OM_uint32
1161_gsspku2u_acquire_cred(OM_uint32 * minor_status,
1162		       const gss_name_t desired_name,
1163		       OM_uint32 time_req,
1164		       const gss_OID_set desired_mechs,
1165		       gss_cred_usage_t cred_usage,
1166		       gss_cred_id_t * output_cred_handle,
1167		       gss_OID_set * actual_mechs,
1168		       OM_uint32 * time_rec)
1169{
1170    krb5_context context;
1171    gsskrb5_cred handle;
1172    hx509_query *q;
1173    hx509_certs certs = NULL;
1174    OM_uint32 ret;
1175    krb5_principal name = (krb5_principal)desired_name;
1176
1177    /* remove non-options from cred_usage */
1178    cred_usage = (cred_usage & GSS_C_OPTION_MASK);
1179
1180    if (cred_usage != GSS_C_ACCEPT && cred_usage != GSS_C_INITIATE && cred_usage != GSS_C_BOTH) {
1181	*minor_status = GSS_KRB5_S_G_BAD_USAGE;
1182	return GSS_S_FAILURE;
1183    }
1184
1185    GSSAPI_KRB5_INIT(&context);
1186
1187    *output_cred_handle = NULL;
1188    if (time_rec)
1189	*time_rec = GSS_C_INDEFINITE;
1190    if (actual_mechs)
1191	*actual_mechs = GSS_C_NO_OID_SET;
1192
1193    /*
1194     * We can't acquire credential for specific names that are not
1195     * PKU2U names, so don't try.
1196     */
1197
1198    if (name && !krb5_principal_is_pku2u(context, name)) {
1199	*minor_status = 0;
1200	return GSS_S_BAD_NAME;
1201    }
1202
1203    handle = calloc(1, sizeof(*handle));
1204    if (handle == NULL)
1205	return (GSS_S_FAILURE);
1206
1207    HEIMDAL_MUTEX_init(&handle->cred_id_mutex);
1208
1209    handle->usage = cred_usage;
1210
1211    if ((cred_usage == GSS_C_INITIATE) || (cred_usage == GSS_C_BOTH)) {
1212	struct search s;
1213
1214	ret = hx509_certs_init(context->hx509ctx, "KEYCHAIN:", 0, NULL, &certs);
1215	if (ret) {
1216	    *minor_status = ret;
1217	    goto fail;
1218	}
1219
1220	ret = hx509_query_alloc(context->hx509ctx, &q);
1221	if (ret) {
1222	    *minor_status = ret;
1223	    goto fail;
1224	}
1225
1226	hx509_query_match_option(q, HX509_QUERY_OPTION_PRIVATE_KEY);
1227	hx509_query_match_option(q, HX509_QUERY_OPTION_KU_DIGITALSIGNATURE);
1228
1229	if (name) {
1230	    s.context = context;
1231	    s.principal = name;
1232	    hx509_query_match_cmp_func(q, match_pkinit_san, &s);
1233	}
1234
1235	ret = _krb5_pk_find_cert(context, 1, certs, q, &handle->cert);
1236	hx509_query_free(context->hx509ctx, q);
1237	if (ret) {
1238	    *minor_status = ret;
1239	    goto fail;
1240	}
1241
1242	if (name)
1243	    ret = krb5_copy_principal(context, name, &handle->principal);
1244	else
1245	    ret = _gsspku2u_principal(context, handle->cert, &handle->principal);
1246	if (ret) {
1247	    *minor_status = ret;
1248	    goto fail;
1249	}
1250
1251    }
1252
1253    if ((cred_usage == GSS_C_ACCEPT) || (cred_usage == GSS_C_BOTH)) {
1254	ret = get_keytab(context, handle, 1);
1255	if (ret) {
1256	    *minor_status = ret;
1257	    goto fail;
1258	}
1259    }
1260    if (certs)
1261	hx509_certs_free(&certs);
1262
1263    *output_cred_handle = (gss_cred_id_t)handle;
1264    return GSS_S_COMPLETE;
1265
1266 fail:
1267    if (certs)
1268	hx509_certs_free(&certs);
1269    if (handle->keytab)
1270	krb5_kt_close(context, handle->keytab);
1271    HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
1272    free(handle);
1273
1274    return GSS_S_FAILURE;
1275}
1276
1277#endif
1278