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