xref: /netbsd-src/crypto/external/bsd/heimdal/dist/lib/krb5/get_for_creds.c (revision 241bea01a19bbb306af27777a870b86d41cb3fda)
1 /*	$NetBSD: get_for_creds.c,v 1.3 2019/12/15 22:50:50 christos Exp $	*/
2 
3 /*
4  * Copyright (c) 1997 - 2004 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 "krb5_locl.h"
37 
38 static krb5_error_code
add_addrs(krb5_context context,krb5_addresses * addr,struct addrinfo * ai)39 add_addrs(krb5_context context,
40 	  krb5_addresses *addr,
41 	  struct addrinfo *ai)
42 {
43     krb5_error_code ret;
44     unsigned n, i;
45     void *tmp;
46     struct addrinfo *a;
47 
48     n = 0;
49     for (a = ai; a != NULL; a = a->ai_next)
50 	++n;
51 
52     tmp = realloc(addr->val, (addr->len + n) * sizeof(*addr->val));
53     if (tmp == NULL && (addr->len + n) != 0) {
54 	ret = krb5_enomem(context);
55 	goto fail;
56     }
57     addr->val = tmp;
58     for (i = addr->len; i < (addr->len + n); ++i) {
59 	addr->val[i].addr_type = 0;
60 	krb5_data_zero(&addr->val[i].address);
61     }
62     i = addr->len;
63     for (a = ai; a != NULL; a = a->ai_next) {
64 	krb5_address ad;
65 
66 	ret = krb5_sockaddr2address (context, a->ai_addr, &ad);
67 	if (ret == 0) {
68 	    if (krb5_address_search(context, &ad, addr))
69 		krb5_free_address(context, &ad);
70 	    else
71 		addr->val[i++] = ad;
72 	}
73 	else if (ret == KRB5_PROG_ATYPE_NOSUPP)
74 	    krb5_clear_error_message (context);
75 	else
76 	    goto fail;
77 	addr->len = i;
78     }
79     return 0;
80 fail:
81     krb5_free_addresses (context, addr);
82     return ret;
83 }
84 
85 /**
86  * Forward credentials for client to host hostname , making them
87  * forwardable if forwardable, and returning the blob of data to sent
88  * in out_data.  If hostname == NULL, pick it from server.
89  *
90  * @param context A kerberos 5 context.
91  * @param auth_context the auth context with the key to encrypt the out_data.
92  * @param hostname the host to forward the tickets too.
93  * @param client the client to delegate from.
94  * @param server the server to delegate the credential too.
95  * @param ccache credential cache to use.
96  * @param forwardable make the forwarded ticket forwabledable.
97  * @param out_data the resulting credential.
98  *
99  * @return Return an error code or 0.
100  *
101  * @ingroup krb5_credential
102  */
103 
104 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
krb5_fwd_tgt_creds(krb5_context context,krb5_auth_context auth_context,const char * hostname,krb5_principal client,krb5_principal server,krb5_ccache ccache,int forwardable,krb5_data * out_data)105 krb5_fwd_tgt_creds (krb5_context	context,
106 		    krb5_auth_context	auth_context,
107 		    const char		*hostname,
108 		    krb5_principal	client,
109 		    krb5_principal	server,
110 		    krb5_ccache		ccache,
111 		    int			forwardable,
112 		    krb5_data		*out_data)
113 {
114     krb5_flags flags = 0;
115     krb5_creds creds;
116     krb5_error_code ret;
117     krb5_const_realm client_realm;
118 
119     flags |= KDC_OPT_FORWARDED;
120 
121     if (forwardable)
122 	flags |= KDC_OPT_FORWARDABLE;
123 
124     if (hostname == NULL &&
125 	krb5_principal_get_type(context, server) == KRB5_NT_SRV_HST) {
126 	const char *inst = krb5_principal_get_comp_string(context, server, 0);
127 	const char *host = krb5_principal_get_comp_string(context, server, 1);
128 
129 	if (inst != NULL &&
130 	    strcmp(inst, "host") == 0 &&
131 	    host != NULL &&
132 	    krb5_principal_get_comp_string(context, server, 2) == NULL)
133 	    hostname = host;
134     }
135 
136     client_realm = krb5_principal_get_realm(context, client);
137 
138     memset (&creds, 0, sizeof(creds));
139     creds.client = client;
140 
141     ret = krb5_make_principal(context,
142 			      &creds.server,
143 			      client_realm,
144 			      KRB5_TGS_NAME,
145 			      client_realm,
146 			      NULL);
147     if (ret)
148 	return ret;
149 
150     ret = krb5_get_forwarded_creds (context,
151 				    auth_context,
152 				    ccache,
153 				    flags,
154 				    hostname,
155 				    &creds,
156 				    out_data);
157     return ret;
158 }
159 
160 /**
161  * Gets tickets forwarded to hostname. If the tickets that are
162  * forwarded are address-less, the forwarded tickets will also be
163  * address-less.
164  *
165  * If the ticket have any address, hostname will be used for figure
166  * out the address to forward the ticket too. This since this might
167  * use DNS, its insecure and also doesn't represent configured all
168  * addresses of the host. For example, the host might have two
169  * adresses, one IPv4 and one IPv6 address where the later is not
170  * published in DNS. This IPv6 address might be used communications
171  * and thus the resulting ticket useless.
172  *
173  * @param context A kerberos 5 context.
174  * @param auth_context the auth context with the key to encrypt the out_data.
175  * @param ccache credential cache to use
176  * @param flags the flags to control the resulting ticket flags
177  * @param hostname the host to forward the tickets too.
178  * @param in_creds the in client and server ticket names.  The client
179  * and server components forwarded to the remote host.
180  * @param out_data the resulting credential.
181  *
182  * @return Return an error code or 0.
183  *
184  * @ingroup krb5_credential
185  */
186 
187 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
krb5_get_forwarded_creds(krb5_context context,krb5_auth_context auth_context,krb5_ccache ccache,krb5_flags flags,const char * hostname,krb5_creds * in_creds,krb5_data * out_data)188 krb5_get_forwarded_creds (krb5_context	    context,
189 			  krb5_auth_context auth_context,
190 			  krb5_ccache       ccache,
191 			  krb5_flags        flags,
192 			  const char        *hostname,
193 			  krb5_creds        *in_creds,
194 			  krb5_data         *out_data)
195 {
196     krb5_error_code ret;
197     krb5_creds *out_creds;
198     krb5_addresses addrs, *paddrs;
199     KRB_CRED cred;
200     KrbCredInfo *krb_cred_info;
201     EncKrbCredPart enc_krb_cred_part;
202     size_t len;
203     unsigned char *buf;
204     size_t buf_size;
205     krb5_kdc_flags kdc_flags;
206     krb5_crypto crypto;
207     struct addrinfo *ai;
208     krb5_creds *ticket;
209 
210     paddrs = NULL;
211     addrs.len = 0;
212     addrs.val = NULL;
213 
214     ret = krb5_get_credentials(context, 0, ccache, in_creds, &ticket);
215     if(ret == 0) {
216 	if (ticket->addresses.len)
217 	    paddrs = &addrs;
218 	krb5_free_creds (context, ticket);
219     } else {
220 	krb5_boolean noaddr;
221 	krb5_appdefault_boolean(context, NULL,
222 				krb5_principal_get_realm(context,
223 							 in_creds->client),
224 				"no-addresses", KRB5_ADDRESSLESS_DEFAULT,
225 				&noaddr);
226 	if (!noaddr)
227 	    paddrs = &addrs;
228     }
229 
230     /*
231      * If tickets have addresses, get the address of the remote host.
232      */
233 
234     if (paddrs != NULL) {
235 
236 	ret = getaddrinfo (hostname, NULL, NULL, &ai);
237 	if (ret) {
238 	    krb5_error_code ret2 = krb5_eai_to_heim_errno(ret, errno);
239 	    krb5_set_error_message(context, ret2,
240 				   N_("resolving host %s failed: %s",
241 				      "hostname, error"),
242 				  hostname, gai_strerror(ret));
243 	    return ret2;
244 	}
245 
246 	ret = add_addrs (context, &addrs, ai);
247 	freeaddrinfo (ai);
248 	if (ret)
249 	    return ret;
250     }
251 
252     kdc_flags.b = int2KDCOptions(flags);
253 
254     ret = krb5_get_kdc_cred (context,
255 			     ccache,
256 			     kdc_flags,
257 			     paddrs,
258 			     NULL,
259 			     in_creds,
260 			     &out_creds);
261     krb5_free_addresses (context, &addrs);
262     if (ret)
263 	return ret;
264 
265     memset (&cred, 0, sizeof(cred));
266     cred.pvno = 5;
267     cred.msg_type = krb_cred;
268     ALLOC_SEQ(&cred.tickets, 1);
269     if (cred.tickets.val == NULL) {
270 	ret = krb5_enomem(context);
271 	goto out2;
272     }
273     ret = decode_Ticket(out_creds->ticket.data,
274 			out_creds->ticket.length,
275 			cred.tickets.val, &len);
276     if (ret)
277 	goto out3;
278 
279     memset (&enc_krb_cred_part, 0, sizeof(enc_krb_cred_part));
280     ALLOC_SEQ(&enc_krb_cred_part.ticket_info, 1);
281     if (enc_krb_cred_part.ticket_info.val == NULL) {
282 	ret = krb5_enomem(context);
283 	goto out4;
284     }
285 
286     if (auth_context->flags & KRB5_AUTH_CONTEXT_DO_TIME) {
287 	krb5_timestamp sec;
288 	int32_t usec;
289 
290 	krb5_us_timeofday (context, &sec, &usec);
291 
292 	ALLOC(enc_krb_cred_part.timestamp, 1);
293 	if (enc_krb_cred_part.timestamp == NULL) {
294 	    ret = krb5_enomem(context);
295 	    goto out4;
296 	}
297 	*enc_krb_cred_part.timestamp = sec;
298 	ALLOC(enc_krb_cred_part.usec, 1);
299 	if (enc_krb_cred_part.usec == NULL) {
300 	    ret = krb5_enomem(context);
301 	    goto out4;
302 	}
303 	*enc_krb_cred_part.usec      = usec;
304     } else {
305 	enc_krb_cred_part.timestamp = NULL;
306 	enc_krb_cred_part.usec = NULL;
307     }
308 
309     if (auth_context->local_address && auth_context->local_port && paddrs) {
310 
311 	ret = krb5_make_addrport (context,
312 				  &enc_krb_cred_part.s_address,
313 				  auth_context->local_address,
314 				  auth_context->local_port);
315 	if (ret)
316 	    goto out4;
317     }
318 
319     if (auth_context->remote_address) {
320 	if (auth_context->remote_port) {
321 	    krb5_boolean noaddr;
322 	    krb5_const_realm srealm;
323 
324 	    srealm = krb5_principal_get_realm(context, out_creds->server);
325 	    /* Is this correct, and should we use the paddrs == NULL
326                trick here as well? Having an address-less ticket may
327                indicate that we don't know our own global address, but
328                it does not necessary mean that we don't know the
329                server's. */
330 	    krb5_appdefault_boolean(context, NULL, srealm, "no-addresses",
331 				    FALSE, &noaddr);
332 	    if (!noaddr) {
333 		ret = krb5_make_addrport (context,
334 					  &enc_krb_cred_part.r_address,
335 					  auth_context->remote_address,
336 					  auth_context->remote_port);
337 		if (ret)
338 		    goto out4;
339 	    }
340 	} else {
341 	    ALLOC(enc_krb_cred_part.r_address, 1);
342 	    if (enc_krb_cred_part.r_address == NULL) {
343 		ret = krb5_enomem(context);
344 		goto out4;
345 	    }
346 
347 	    ret = krb5_copy_address (context, auth_context->remote_address,
348 				     enc_krb_cred_part.r_address);
349 	    if (ret)
350 		goto out4;
351 	}
352     }
353 
354     /* fill ticket_info.val[0] */
355 
356     enc_krb_cred_part.ticket_info.len = 1;
357 
358     krb_cred_info = enc_krb_cred_part.ticket_info.val;
359 
360     ret = copy_EncryptionKey (&out_creds->session, &krb_cred_info->key);
361     if (ret)
362 	goto out4;
363     ALLOC(krb_cred_info->prealm, 1);
364     ret = copy_Realm (&out_creds->client->realm, krb_cred_info->prealm);
365     if (ret)
366 	goto out4;
367     ALLOC(krb_cred_info->pname, 1);
368     ret = copy_PrincipalName(&out_creds->client->name, krb_cred_info->pname);
369     if (ret)
370 	goto out4;
371     ALLOC(krb_cred_info->flags, 1);
372     *krb_cred_info->flags          = out_creds->flags.b;
373     ALLOC(krb_cred_info->authtime, 1);
374     *krb_cred_info->authtime       = out_creds->times.authtime;
375     ALLOC(krb_cred_info->starttime, 1);
376     *krb_cred_info->starttime      = out_creds->times.starttime;
377     ALLOC(krb_cred_info->endtime, 1);
378     *krb_cred_info->endtime        = out_creds->times.endtime;
379     ALLOC(krb_cred_info->renew_till, 1);
380     *krb_cred_info->renew_till = out_creds->times.renew_till;
381     ALLOC(krb_cred_info->srealm, 1);
382     ret = copy_Realm (&out_creds->server->realm, krb_cred_info->srealm);
383     if (ret)
384 	goto out4;
385     ALLOC(krb_cred_info->sname, 1);
386     ret = copy_PrincipalName (&out_creds->server->name, krb_cred_info->sname);
387     if (ret)
388 	goto out4;
389     ALLOC(krb_cred_info->caddr, 1);
390     ret = copy_HostAddresses (&out_creds->addresses, krb_cred_info->caddr);
391     if (ret)
392 	goto out4;
393 
394     krb5_free_creds (context, out_creds);
395 
396     /* encode EncKrbCredPart */
397 
398     ASN1_MALLOC_ENCODE(EncKrbCredPart, buf, buf_size,
399 		       &enc_krb_cred_part, &len, ret);
400     free_EncKrbCredPart (&enc_krb_cred_part);
401     if (ret) {
402 	free_KRB_CRED(&cred);
403 	return ret;
404     }
405     if(buf_size != len)
406 	krb5_abortx(context, "internal error in ASN.1 encoder");
407 
408     /**
409      * Some older of the MIT gssapi library used clear-text tickets
410      * (warped inside AP-REQ encryption), use the krb5_auth_context
411      * flag KRB5_AUTH_CONTEXT_CLEAR_FORWARDED_CRED to support those
412      * tickets. The session key is used otherwise to encrypt the
413      * forwarded ticket.
414      */
415 
416     if (auth_context->flags & KRB5_AUTH_CONTEXT_CLEAR_FORWARDED_CRED) {
417 	cred.enc_part.etype = KRB5_ENCTYPE_NULL;
418 	cred.enc_part.kvno = NULL;
419 	cred.enc_part.cipher.data = buf;
420 	cred.enc_part.cipher.length = buf_size;
421     } else {
422 	/*
423 	 * Here older versions then 0.7.2 of Heimdal used the local or
424 	 * remote subkey. That is wrong, the session key should be
425 	 * used. Heimdal 0.7.2 and newer have code to try both in the
426 	 * receiving end.
427 	 */
428 
429 	ret = krb5_crypto_init(context, auth_context->keyblock, 0, &crypto);
430 	if (ret) {
431 	    free(buf);
432 	    free_KRB_CRED(&cred);
433 	    return ret;
434 	}
435 	ret = krb5_encrypt_EncryptedData (context,
436 					  crypto,
437 					  KRB5_KU_KRB_CRED,
438 					  buf,
439 					  len,
440 					  0,
441 					  &cred.enc_part);
442 	free(buf);
443 	krb5_crypto_destroy(context, crypto);
444 	if (ret) {
445 	    free_KRB_CRED(&cred);
446 	    return ret;
447 	}
448     }
449 
450     ASN1_MALLOC_ENCODE(KRB_CRED, buf, buf_size, &cred, &len, ret);
451     free_KRB_CRED (&cred);
452     if (ret)
453 	return ret;
454     if(buf_size != len)
455 	krb5_abortx(context, "internal error in ASN.1 encoder");
456     out_data->length = len;
457     out_data->data   = buf;
458     return 0;
459  out4:
460     free_EncKrbCredPart(&enc_krb_cred_part);
461  out3:
462     free_KRB_CRED(&cred);
463  out2:
464     krb5_free_creds (context, out_creds);
465     return ret;
466 }
467