1 /* $NetBSD: acquire_cred.c,v 1.3 2023/06/19 21:41:43 christos Exp $ */
2
3 /*
4 * Copyright (c) 1997 - 2005 Kungliga Tekniska Högskolan
5 * (Royal Institute of Technology, Stockholm, Sweden).
6 * 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
38 OM_uint32
__gsskrb5_ccache_lifetime(OM_uint32 * minor_status,krb5_context context,krb5_ccache id,krb5_principal principal,OM_uint32 * lifetime)39 __gsskrb5_ccache_lifetime(OM_uint32 *minor_status,
40 krb5_context context,
41 krb5_ccache id,
42 krb5_principal principal,
43 OM_uint32 *lifetime)
44 {
45 krb5_error_code kret;
46 time_t left;
47
48 kret = krb5_cc_get_lifetime(context, id, &left);
49 if (kret) {
50 *minor_status = kret;
51 return GSS_S_FAILURE;
52 }
53
54 *lifetime = left;
55
56 return GSS_S_COMPLETE;
57 }
58
59
60
61
62 static krb5_error_code
get_keytab(krb5_context context,krb5_keytab * keytab)63 get_keytab(krb5_context context, krb5_keytab *keytab)
64 {
65 krb5_error_code kret;
66
67 HEIMDAL_MUTEX_lock(&gssapi_keytab_mutex);
68
69 if (_gsskrb5_keytab != NULL) {
70 char *name = NULL;
71
72 kret = krb5_kt_get_full_name(context, _gsskrb5_keytab, &name);
73 if (kret == 0) {
74 kret = krb5_kt_resolve(context, name, keytab);
75 krb5_xfree(name);
76 }
77 } else
78 kret = krb5_kt_default(context, keytab);
79
80 HEIMDAL_MUTEX_unlock(&gssapi_keytab_mutex);
81
82 return (kret);
83 }
84
85 /*
86 * This function produces a cred with a MEMORY ccache containing a TGT
87 * acquired with a password.
88 */
89 static OM_uint32
acquire_cred_with_password(OM_uint32 * minor_status,krb5_context context,const char * password,OM_uint32 time_req,gss_const_OID desired_mech,gss_cred_usage_t cred_usage,gsskrb5_cred handle)90 acquire_cred_with_password(OM_uint32 *minor_status,
91 krb5_context context,
92 const char *password,
93 OM_uint32 time_req,
94 gss_const_OID desired_mech,
95 gss_cred_usage_t cred_usage,
96 gsskrb5_cred handle)
97 {
98 OM_uint32 ret = GSS_S_FAILURE;
99 krb5_creds cred;
100 krb5_get_init_creds_opt *opt;
101 krb5_ccache ccache = NULL;
102 krb5_error_code kret;
103 time_t now;
104 OM_uint32 left;
105 const char *realm;
106
107 if (cred_usage == GSS_C_ACCEPT) {
108 /*
109 * TODO: Here we should eventually support user2user (when we get
110 * support for that via an extension to the mechanism
111 * allowing for more than two security context tokens),
112 * and/or new unique MEMORY keytabs (we have MEMORY keytab
113 * support, but we don't have a keytab equivalent of
114 * krb5_cc_new_unique()). Either way, for now we can't
115 * support this.
116 */
117 *minor_status = ENOTSUP; /* XXX Better error? */
118 return GSS_S_FAILURE;
119 }
120
121 memset(&cred, 0, sizeof(cred));
122
123 if (handle->principal == NULL) {
124 kret = krb5_get_default_principal(context, &handle->principal);
125 if (kret)
126 goto end;
127 }
128 kret = krb5_get_init_creds_opt_alloc(context, &opt);
129 if (kret)
130 goto end;
131
132 realm = krb5_principal_get_realm(context, handle->principal);
133
134 krb5_get_init_creds_opt_set_default_flags(context, "gss_krb5", realm, opt);
135
136 /*
137 * Get the current time before the AS exchange so we don't
138 * accidentally end up returning a value that puts advertised
139 * expiration past the real expiration.
140 *
141 * We need to do this because krb5_cc_get_lifetime() returns a
142 * relative time that we need to add to the current time. We ought
143 * to have a version of krb5_cc_get_lifetime() that returns absolute
144 * time...
145 */
146 krb5_timeofday(context, &now);
147
148 kret = krb5_get_init_creds_password(context, &cred, handle->principal,
149 password, NULL, NULL, 0, NULL, opt);
150 krb5_get_init_creds_opt_free(context, opt);
151 if (kret)
152 goto end;
153
154 kret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, &ccache);
155 if (kret)
156 goto end;
157
158 kret = krb5_cc_initialize(context, ccache, cred.client);
159 if (kret)
160 goto end;
161
162 kret = krb5_cc_store_cred(context, ccache, &cred);
163 if (kret)
164 goto end;
165
166 handle->cred_flags |= GSS_CF_DESTROY_CRED_ON_RELEASE;
167
168 ret = __gsskrb5_ccache_lifetime(minor_status, context, ccache,
169 handle->principal, &left);
170 if (ret != GSS_S_COMPLETE)
171 goto end;
172 handle->endtime = now + left;
173 handle->ccache = ccache;
174 ccache = NULL;
175 ret = GSS_S_COMPLETE;
176 kret = 0;
177
178 end:
179 if (ccache != NULL)
180 krb5_cc_destroy(context, ccache);
181 if (cred.client != NULL)
182 krb5_free_cred_contents(context, &cred);
183 if (ret != GSS_S_COMPLETE && kret != 0)
184 *minor_status = kret;
185 return (ret);
186 }
187
188 /*
189 * Acquires an initiator credential from a ccache or using a keytab.
190 */
191 static OM_uint32
acquire_initiator_cred(OM_uint32 * minor_status,krb5_context context,OM_uint32 time_req,gss_const_OID desired_mech,gss_cred_usage_t cred_usage,gsskrb5_cred handle)192 acquire_initiator_cred(OM_uint32 *minor_status,
193 krb5_context context,
194 OM_uint32 time_req,
195 gss_const_OID desired_mech,
196 gss_cred_usage_t cred_usage,
197 gsskrb5_cred handle)
198 {
199 OM_uint32 ret = GSS_S_FAILURE;
200 krb5_creds cred;
201 krb5_get_init_creds_opt *opt;
202 krb5_principal def_princ = NULL;
203 krb5_ccache def_ccache = NULL;
204 krb5_ccache ccache = NULL; /* we may store into this ccache */
205 krb5_keytab keytab = NULL;
206 krb5_error_code kret = 0;
207 OM_uint32 left;
208 time_t lifetime = 0;
209 time_t now;
210
211 memset(&cred, 0, sizeof(cred));
212
213 /*
214 * Get current time early so we can set handle->endtime to a value that
215 * cannot accidentally be past the real endtime. We need a variant of
216 * krb5_cc_get_lifetime() that returns absolute endtime.
217 */
218 krb5_timeofday(context, &now);
219
220 /*
221 * First look for a ccache that has the desired_name (which may be
222 * the default credential name).
223 *
224 * If we don't have an unexpired credential, acquire one with a
225 * keytab.
226 *
227 * If we acquire one with a keytab, save it in the ccache we found
228 * with the expired credential, if any.
229 *
230 * If we don't have any such ccache, then use a MEMORY ccache.
231 */
232
233 if (handle->principal != NULL) {
234 /*
235 * Not default credential case. See if we can find a ccache in
236 * the cccol for the desired_name.
237 */
238 kret = krb5_cc_cache_match(context,
239 handle->principal,
240 &ccache);
241 if (kret == 0) {
242 kret = krb5_cc_get_lifetime(context, ccache, &lifetime);
243 if (kret == 0) {
244 if (lifetime > 0)
245 goto found;
246 else
247 goto try_keytab;
248 }
249 }
250 /*
251 * Fall through. We shouldn't find this in the default ccache
252 * either, but we'll give it a try, then we'll try using a keytab.
253 */
254 }
255
256 /*
257 * Either desired_name was GSS_C_NO_NAME (default cred) or
258 * krb5_cc_cache_match() failed (or found expired).
259 */
260 kret = krb5_cc_default(context, &def_ccache);
261 if (kret != 0)
262 goto try_keytab;
263 kret = krb5_cc_get_lifetime(context, def_ccache, &lifetime);
264 if (kret != 0)
265 lifetime = 0;
266 kret = krb5_cc_get_principal(context, def_ccache, &def_princ);
267 if (kret != 0)
268 goto try_keytab;
269 /*
270 * Have a default ccache; see if it matches desired_name.
271 */
272 if (handle->principal == NULL ||
273 krb5_principal_compare(context, handle->principal,
274 def_princ) == TRUE) {
275 /*
276 * It matches.
277 *
278 * If we end up trying a keytab then we can write the result to
279 * the default ccache.
280 */
281 if (handle->principal == NULL) {
282 kret = krb5_copy_principal(context, def_princ, &handle->principal);
283 if (kret)
284 goto end;
285 }
286 if (ccache != NULL)
287 krb5_cc_close(context, ccache);
288 ccache = def_ccache;
289 def_ccache = NULL;
290 if (lifetime > 0)
291 goto found;
292 /* else we fall through and try using a keytab */
293 }
294
295 try_keytab:
296 if (handle->principal == NULL) {
297 /* We need to know what client principal to use */
298 kret = krb5_get_default_principal(context, &handle->principal);
299 if (kret)
300 goto end;
301 }
302 kret = get_keytab(context, &keytab);
303 if (kret)
304 goto end;
305
306 kret = krb5_get_init_creds_opt_alloc(context, &opt);
307 if (kret)
308 goto end;
309 krb5_timeofday(context, &now);
310 kret = krb5_get_init_creds_keytab(context, &cred, handle->principal,
311 keytab, 0, NULL, opt);
312 krb5_get_init_creds_opt_free(context, opt);
313 if (kret)
314 goto end;
315
316 /*
317 * We got a credential with a keytab. Save it if we can.
318 */
319 if (ccache == NULL) {
320 /*
321 * There's no ccache we can overwrite with the credentials we acquired
322 * with a keytab. We'll use a MEMORY ccache then.
323 *
324 * Note that an application that falls into this repeatedly will do an
325 * AS exchange every time it acquires a credential handle. Hopefully
326 * this doesn't happen much. A workaround is to kinit -k once so that
327 * we always re-initialize the matched/default ccache here. I.e., once
328 * there's a FILE/DIR ccache, we'll keep it frash automatically if we
329 * have a keytab, but if there's no FILE/DIR ccache, then we'll
330 * get a fresh credential *every* time we're asked.
331 */
332 kret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, &ccache);
333 if (kret)
334 goto end;
335 handle->cred_flags |= GSS_CF_DESTROY_CRED_ON_RELEASE;
336 } /* else we'll re-initialize whichever ccache we matched above */
337
338 kret = krb5_cc_initialize(context, ccache, cred.client);
339 if (kret)
340 goto end;
341 kret = krb5_cc_store_cred(context, ccache, &cred);
342 if (kret)
343 goto end;
344
345 found:
346 assert(handle->principal != NULL);
347 ret = __gsskrb5_ccache_lifetime(minor_status, context, ccache,
348 handle->principal, &left);
349 if (ret != GSS_S_COMPLETE)
350 goto end;
351 handle->endtime = now + left;
352 handle->ccache = ccache;
353 ccache = NULL;
354 ret = GSS_S_COMPLETE;
355 kret = 0;
356
357 end:
358 if (ccache != NULL) {
359 if ((handle->cred_flags & GSS_CF_DESTROY_CRED_ON_RELEASE) != 0)
360 krb5_cc_destroy(context, ccache);
361 else
362 krb5_cc_close(context, ccache);
363 }
364 if (def_ccache != NULL)
365 krb5_cc_close(context, def_ccache);
366 if (cred.client != NULL)
367 krb5_free_cred_contents(context, &cred);
368 if (def_princ != NULL)
369 krb5_free_principal(context, def_princ);
370 if (keytab != NULL)
371 krb5_kt_close(context, keytab);
372 if (ret != GSS_S_COMPLETE && kret != 0)
373 *minor_status = kret;
374 return (ret);
375 }
376
377 static OM_uint32
acquire_acceptor_cred(OM_uint32 * minor_status,krb5_context context,OM_uint32 time_req,gss_const_OID desired_mech,gss_cred_usage_t cred_usage,gsskrb5_cred handle)378 acquire_acceptor_cred(OM_uint32 * minor_status,
379 krb5_context context,
380 OM_uint32 time_req,
381 gss_const_OID desired_mech,
382 gss_cred_usage_t cred_usage,
383 gsskrb5_cred handle)
384 {
385 OM_uint32 ret;
386 krb5_error_code kret;
387
388 ret = GSS_S_FAILURE;
389
390 kret = get_keytab(context, &handle->keytab);
391 if (kret)
392 goto end;
393
394 /* check that the requested principal exists in the keytab */
395 if (handle->principal) {
396 krb5_keytab_entry entry;
397
398 kret = krb5_kt_get_entry(context, handle->keytab,
399 handle->principal, 0, 0, &entry);
400 if (kret)
401 goto end;
402 krb5_kt_free_entry(context, &entry);
403 ret = GSS_S_COMPLETE;
404 } else {
405 /*
406 * Check if there is at least one entry in the keytab before
407 * declaring it as an useful keytab.
408 */
409 krb5_keytab_entry tmp;
410 krb5_kt_cursor c;
411
412 kret = krb5_kt_start_seq_get (context, handle->keytab, &c);
413 if (kret)
414 goto end;
415 if (krb5_kt_next_entry(context, handle->keytab, &tmp, &c) == 0) {
416 krb5_kt_free_entry(context, &tmp);
417 ret = GSS_S_COMPLETE; /* ok found one entry */
418 }
419 krb5_kt_end_seq_get (context, handle->keytab, &c);
420 }
421 end:
422 if (ret != GSS_S_COMPLETE) {
423 if (handle->keytab != NULL)
424 krb5_kt_close(context, handle->keytab);
425 if (kret != 0) {
426 *minor_status = kret;
427 }
428 }
429 return (ret);
430 }
431
_gsskrb5_acquire_cred(OM_uint32 * minor_status,gss_const_name_t desired_name,OM_uint32 time_req,const gss_OID_set desired_mechs,gss_cred_usage_t cred_usage,gss_cred_id_t * output_cred_handle,gss_OID_set * actual_mechs,OM_uint32 * time_rec)432 OM_uint32 GSSAPI_CALLCONV _gsskrb5_acquire_cred
433 (OM_uint32 * minor_status,
434 gss_const_name_t desired_name,
435 OM_uint32 time_req,
436 const gss_OID_set desired_mechs,
437 gss_cred_usage_t cred_usage,
438 gss_cred_id_t * output_cred_handle,
439 gss_OID_set * actual_mechs,
440 OM_uint32 * time_rec
441 )
442 {
443 OM_uint32 ret;
444
445 if (desired_mechs) {
446 int present = 0;
447
448 ret = gss_test_oid_set_member(minor_status, GSS_KRB5_MECHANISM,
449 desired_mechs, &present);
450 if (ret)
451 return ret;
452 if (!present) {
453 *minor_status = 0;
454 return GSS_S_BAD_MECH;
455 }
456 }
457
458 ret = _gsskrb5_acquire_cred_ext(minor_status,
459 desired_name,
460 GSS_C_NO_OID,
461 NULL,
462 time_req,
463 GSS_KRB5_MECHANISM,
464 cred_usage,
465 output_cred_handle);
466 if (ret)
467 return ret;
468
469
470 ret = _gsskrb5_inquire_cred(minor_status, *output_cred_handle,
471 NULL, time_rec, NULL, actual_mechs);
472 if (ret) {
473 OM_uint32 tmp;
474 _gsskrb5_release_cred(&tmp, output_cred_handle);
475 }
476
477 return ret;
478 }
479
_gsskrb5_acquire_cred_ext(OM_uint32 * minor_status,gss_const_name_t desired_name,gss_const_OID credential_type,const void * credential_data,OM_uint32 time_req,gss_const_OID desired_mech,gss_cred_usage_t cred_usage,gss_cred_id_t * output_cred_handle)480 OM_uint32 GSSAPI_CALLCONV _gsskrb5_acquire_cred_ext
481 (OM_uint32 * minor_status,
482 gss_const_name_t desired_name,
483 gss_const_OID credential_type,
484 const void *credential_data,
485 OM_uint32 time_req,
486 gss_const_OID desired_mech,
487 gss_cred_usage_t cred_usage,
488 gss_cred_id_t * output_cred_handle
489 )
490 {
491 krb5_context context;
492 gsskrb5_cred handle;
493 OM_uint32 ret;
494
495 cred_usage &= GSS_C_OPTION_MASK;
496
497 if (cred_usage != GSS_C_ACCEPT && cred_usage != GSS_C_INITIATE &&
498 cred_usage != GSS_C_BOTH) {
499 *minor_status = GSS_KRB5_S_G_BAD_USAGE;
500 return GSS_S_FAILURE;
501 }
502
503 GSSAPI_KRB5_INIT(&context);
504
505 *output_cred_handle = GSS_C_NO_CREDENTIAL;
506
507 handle = calloc(1, sizeof(*handle));
508 if (handle == NULL) {
509 *minor_status = ENOMEM;
510 return GSS_S_FAILURE;
511 }
512
513 HEIMDAL_MUTEX_init(&handle->cred_id_mutex);
514
515 if (desired_name != GSS_C_NO_NAME) {
516 ret = _gsskrb5_canon_name(minor_status, context,
517 desired_name, &handle->principal);
518 if (ret) {
519 HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
520 free(handle);
521 return ret;
522 }
523 }
524
525 if (credential_type != GSS_C_NO_OID &&
526 gss_oid_equal(credential_type, GSS_C_CRED_PASSWORD)) {
527 /* Acquire a cred with a password */
528 gss_const_buffer_t pwbuf = credential_data;
529 char *pw;
530
531 if (pwbuf == NULL) {
532 HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
533 free(handle);
534 *minor_status = KRB5_NOCREDS_SUPPLIED; /* see below */
535 return GSS_S_CALL_INACCESSIBLE_READ;
536 }
537
538 /* NUL-terminate the password, if it wasn't already */
539 pw = strndup(pwbuf->value, pwbuf->length);
540 if (pw == NULL) {
541 HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
542 free(handle);
543 *minor_status = krb5_enomem(context);
544 return GSS_S_CALL_INACCESSIBLE_READ;
545 }
546 ret = acquire_cred_with_password(minor_status, context, pw, time_req,
547 desired_mech, cred_usage, handle);
548 free(pw);
549 if (ret != GSS_S_COMPLETE) {
550 HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
551 krb5_free_principal(context, handle->principal);
552 free(handle);
553 return (ret);
554 }
555 } else if (credential_type != GSS_C_NO_OID) {
556 /*
557 * _gss_acquire_cred_ext() called with something other than a password.
558 *
559 * Not supported.
560 *
561 * _gss_acquire_cred_ext() is not a supported public interface, so
562 * we don't have to try too hard as to minor status codes here.
563 */
564 HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
565 free(handle);
566 *minor_status = ENOTSUP;
567 return GSS_S_FAILURE;
568 } else {
569 /*
570 * Acquire a credential from the background credential store (ccache,
571 * keytab).
572 */
573 if (cred_usage == GSS_C_INITIATE || cred_usage == GSS_C_BOTH) {
574 ret = acquire_initiator_cred(minor_status, context, time_req,
575 desired_mech, cred_usage, handle);
576 if (ret != GSS_S_COMPLETE) {
577 HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
578 krb5_free_principal(context, handle->principal);
579 free(handle);
580 return (ret);
581 }
582 }
583 if (cred_usage == GSS_C_ACCEPT || cred_usage == GSS_C_BOTH) {
584 ret = acquire_acceptor_cred(minor_status, context, time_req,
585 desired_mech, cred_usage, handle);
586 if (ret != GSS_S_COMPLETE) {
587 HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
588 krb5_free_principal(context, handle->principal);
589 free(handle);
590 return (ret);
591 }
592 }
593 }
594 ret = gss_create_empty_oid_set(minor_status, &handle->mechanisms);
595 if (ret == GSS_S_COMPLETE)
596 ret = gss_add_oid_set_member(minor_status, GSS_KRB5_MECHANISM,
597 &handle->mechanisms);
598 if (ret != GSS_S_COMPLETE) {
599 if (handle->mechanisms != NULL)
600 gss_release_oid_set(NULL, &handle->mechanisms);
601 HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
602 krb5_free_principal(context, handle->principal);
603 free(handle);
604 return (ret);
605 }
606 handle->usage = cred_usage;
607 *minor_status = 0;
608 *output_cred_handle = (gss_cred_id_t)handle;
609 return (GSS_S_COMPLETE);
610 }
611