Move kerberos_kinit_keyblock_cc to krb5samba lib
[idra/samba.git] / source4 / auth / kerberos / kerberos.c
1 /* 
2    Unix SMB/CIFS implementation.
3    kerberos utility library
4    Copyright (C) Andrew Tridgell 2001
5    Copyright (C) Remus Koos 2001
6    Copyright (C) Nalin Dahyabhai 2004.
7    Copyright (C) Jeremy Allison 2004.
8    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005
9
10    This program is free software; you can redistribute it and/or modify
11    it under the terms of the GNU General Public License as published by
12    the Free Software Foundation; either version 3 of the License, or
13    (at your option) any later version.
14    
15    This program is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18    GNU General Public License for more details.
19    
20    You should have received a copy of the GNU General Public License
21    along with this program.  If not, see <http://www.gnu.org/licenses/>.
22 */
23
24 #include "includes.h"
25 #include "system/kerberos.h"
26 #include "auth/kerberos/kerberos.h"
27
28 #ifdef HAVE_KRB5
29
30 /*
31   simulate a kinit, putting the tgt in the given credentials cache. 
32   Orignally by remus@snapserver.com
33
34   The impersonate_principal is the principal if NULL, or the principal to impersonate
35
36   The self_service, should be the local service (for S4U2Self if impersonate_principal is given).
37
38   The target_service defaults to the krbtgt if NULL, but could be kpasswd/realm or a remote service (for S4U2Proxy)
39
40 */
41  krb5_error_code kerberos_kinit_password_cc(krb5_context ctx, krb5_ccache store_cc,
42                                             krb5_principal init_principal,
43                                             const char *init_password,
44                                             krb5_principal impersonate_principal,
45                                             const char *self_service,
46                                             const char *target_service,
47                                             krb5_get_init_creds_opt *krb_options,
48                                             time_t *expire_time, time_t *kdc_time)
49 {
50         krb5_error_code code = 0;
51         krb5_get_creds_opt options;
52         krb5_principal store_principal;
53         krb5_creds store_creds;
54         krb5_creds *s4u2self_creds;
55         Ticket s4u2self_ticket;
56         size_t s4u2self_ticketlen;
57         krb5_creds *s4u2proxy_creds;
58         krb5_principal self_princ;
59         bool s4u2proxy;
60         krb5_principal target_princ;
61         krb5_ccache tmp_cc;
62         const char *self_realm;
63         krb5_principal blacklist_principal = NULL;
64         krb5_principal whitelist_principal = NULL;
65
66         if (impersonate_principal && self_service == NULL) {
67                 return EINVAL;
68         }
69
70         /*
71          * If we are not impersonating, then get this ticket for the
72          * target service, otherwise a krbtgt, and get the next ticket
73          * for the target
74          */
75         code = krb5_get_init_creds_password(ctx, &store_creds,
76                                             init_principal,
77                                             init_password,
78                                             NULL, NULL,
79                                             0,
80                                             impersonate_principal ? NULL : target_service,
81                                             krb_options);
82         if (code != 0) {
83                 return code;
84         }
85
86         store_principal = init_principal;
87
88         if (impersonate_principal == NULL) {
89                 goto store;
90         }
91
92         /*
93          * We are trying S4U2Self now:
94          *
95          * As we do not want to expose our TGT in the
96          * krb5_ccache, which is also holds the impersonated creds.
97          *
98          * Some low level krb5/gssapi function might use the TGT
99          * identity and let the client act as our machine account.
100          *
101          * We need to avoid that and use a temporary krb5_ccache
102          * in order to pass our TGT to the krb5_get_creds() function.
103          */
104         code = krb5_cc_new_unique(ctx, NULL, NULL, &tmp_cc);
105         if (code != 0) {
106                 krb5_free_cred_contents(ctx, &store_creds);
107                 return code;
108         }
109
110         code = krb5_cc_initialize(ctx, tmp_cc, store_creds.client);
111         if (code != 0) {
112                 krb5_cc_destroy(ctx, tmp_cc);
113                 krb5_free_cred_contents(ctx, &store_creds);
114                 return code;
115         }
116
117         code = krb5_cc_store_cred(ctx, tmp_cc, &store_creds);
118         if (code != 0) {
119                 krb5_free_cred_contents(ctx, &store_creds);
120                 krb5_cc_destroy(ctx, tmp_cc);
121                 return code;
122         }
123
124         /*
125          * we need to remember the client principal of our
126          * TGT and make sure the KDC does not return this
127          * in the impersonated tickets. This can happen
128          * if the KDC does not support S4U2Self and S4U2Proxy.
129          */
130         blacklist_principal = store_creds.client;
131         store_creds.client = NULL;
132         krb5_free_cred_contents(ctx, &store_creds);
133
134         /*
135          * Check if we also need S4U2Proxy or if S4U2Self is
136          * enough in order to get a ticket for the target.
137          */
138         if (target_service == NULL) {
139                 s4u2proxy = false;
140         } else if (strcmp(target_service, self_service) == 0) {
141                 s4u2proxy = false;
142         } else {
143                 s4u2proxy = true;
144         }
145
146         /*
147          * For S4U2Self we need our own service principal,
148          * which belongs to our own realm (available on
149          * our client principal).
150          */
151         self_realm = krb5_principal_get_realm(ctx, init_principal);
152
153         code = krb5_parse_name(ctx, self_service, &self_princ);
154         if (code != 0) {
155                 krb5_free_principal(ctx, blacklist_principal);
156                 krb5_cc_destroy(ctx, tmp_cc);
157                 return code;
158         }
159
160         code = krb5_principal_set_realm(ctx, self_princ, self_realm);
161         if (code != 0) {
162                 krb5_free_principal(ctx, blacklist_principal);
163                 krb5_free_principal(ctx, self_princ);
164                 krb5_cc_destroy(ctx, tmp_cc);
165                 return code;
166         }
167
168         code = krb5_get_creds_opt_alloc(ctx, &options);
169         if (code != 0) {
170                 krb5_free_principal(ctx, blacklist_principal);
171                 krb5_free_principal(ctx, self_princ);
172                 krb5_cc_destroy(ctx, tmp_cc);
173                 return code;
174         }
175
176         if (s4u2proxy) {
177                 /*
178                  * If we want S4U2Proxy, we need the forwardable flag
179                  * on the S4U2Self ticket.
180                  */
181                 krb5_get_creds_opt_set_options(ctx, options, KRB5_GC_FORWARDABLE);
182         }
183
184         code = krb5_get_creds_opt_set_impersonate(ctx, options,
185                                                   impersonate_principal);
186         if (code != 0) {
187                 krb5_get_creds_opt_free(ctx, options);
188                 krb5_free_principal(ctx, blacklist_principal);
189                 krb5_free_principal(ctx, self_princ);
190                 krb5_cc_destroy(ctx, tmp_cc);
191                 return code;
192         }
193
194         code = krb5_get_creds(ctx, options, tmp_cc,
195                               self_princ, &s4u2self_creds);
196         krb5_get_creds_opt_free(ctx, options);
197         krb5_free_principal(ctx, self_princ);
198         if (code != 0) {
199                 krb5_free_principal(ctx, blacklist_principal);
200                 krb5_cc_destroy(ctx, tmp_cc);
201                 return code;
202         }
203
204         if (!s4u2proxy) {
205                 krb5_cc_destroy(ctx, tmp_cc);
206
207                 /*
208                  * Now make sure we store the impersonated principal
209                  * and creds instead of the TGT related stuff
210                  * in the krb5_ccache of the caller.
211                  */
212                 code = krb5_copy_creds_contents(ctx, s4u2self_creds,
213                                                 &store_creds);
214                 krb5_free_creds(ctx, s4u2self_creds);
215                 if (code != 0) {
216                         return code;
217                 }
218
219                 /*
220                  * It's important to store the principal the KDC
221                  * returned, as otherwise the caller would not find
222                  * the S4U2Self ticket in the krb5_ccache lookup.
223                  */
224                 store_principal = store_creds.client;
225                 goto store;
226         }
227
228         /*
229          * We are trying S4U2Proxy:
230          *
231          * We need the ticket from the S4U2Self step
232          * and our TGT in order to get the delegated ticket.
233          */
234         code = decode_Ticket((const uint8_t *)s4u2self_creds->ticket.data,
235                              s4u2self_creds->ticket.length,
236                              &s4u2self_ticket,
237                              &s4u2self_ticketlen);
238         if (code != 0) {
239                 krb5_free_creds(ctx, s4u2self_creds);
240                 krb5_free_principal(ctx, blacklist_principal);
241                 krb5_cc_destroy(ctx, tmp_cc);
242                 return code;
243         }
244
245         /*
246          * we need to remember the client principal of the
247          * S4U2Self stage and as it needs to match the one we
248          * will get for the S4U2Proxy stage. We need this
249          * in order to detect KDCs which does not support S4U2Proxy.
250          */
251         whitelist_principal = s4u2self_creds->client;
252         s4u2self_creds->client = NULL;
253         krb5_free_creds(ctx, s4u2self_creds);
254
255         /*
256          * For S4U2Proxy we also got a target service principal,
257          * which also belongs to our own realm (available on
258          * our client principal).
259          */
260         code = krb5_parse_name(ctx, target_service, &target_princ);
261         if (code != 0) {
262                 free_Ticket(&s4u2self_ticket);
263                 krb5_free_principal(ctx, whitelist_principal);
264                 krb5_free_principal(ctx, blacklist_principal);
265                 krb5_cc_destroy(ctx, tmp_cc);
266                 return code;
267         }
268
269         code = krb5_principal_set_realm(ctx, target_princ, self_realm);
270         if (code != 0) {
271                 free_Ticket(&s4u2self_ticket);
272                 krb5_free_principal(ctx, target_princ);
273                 krb5_free_principal(ctx, whitelist_principal);
274                 krb5_free_principal(ctx, blacklist_principal);
275                 krb5_cc_destroy(ctx, tmp_cc);
276                 return code;
277         }
278
279         code = krb5_get_creds_opt_alloc(ctx, &options);
280         if (code != 0) {
281                 free_Ticket(&s4u2self_ticket);
282                 krb5_free_principal(ctx, target_princ);
283                 krb5_free_principal(ctx, whitelist_principal);
284                 krb5_free_principal(ctx, blacklist_principal);
285                 krb5_cc_destroy(ctx, tmp_cc);
286                 return code;
287         }
288
289         krb5_get_creds_opt_set_options(ctx, options, KRB5_GC_FORWARDABLE);
290         krb5_get_creds_opt_set_options(ctx, options, KRB5_GC_CONSTRAINED_DELEGATION);
291
292         code = krb5_get_creds_opt_set_ticket(ctx, options, &s4u2self_ticket);
293         free_Ticket(&s4u2self_ticket);
294         if (code != 0) {
295                 krb5_get_creds_opt_free(ctx, options);
296                 krb5_free_principal(ctx, target_princ);
297                 krb5_free_principal(ctx, whitelist_principal);
298                 krb5_free_principal(ctx, blacklist_principal);
299                 krb5_cc_destroy(ctx, tmp_cc);
300                 return code;
301         }
302
303         code = krb5_get_creds(ctx, options, tmp_cc,
304                               target_princ, &s4u2proxy_creds);
305         krb5_get_creds_opt_free(ctx, options);
306         krb5_free_principal(ctx, target_princ);
307         krb5_cc_destroy(ctx, tmp_cc);
308         if (code != 0) {
309                 krb5_free_principal(ctx, whitelist_principal);
310                 krb5_free_principal(ctx, blacklist_principal);
311                 return code;
312         }
313
314         /*
315          * Now make sure we store the impersonated principal
316          * and creds instead of the TGT related stuff
317          * in the krb5_ccache of the caller.
318          */
319         code = krb5_copy_creds_contents(ctx, s4u2proxy_creds,
320                                         &store_creds);
321         krb5_free_creds(ctx, s4u2proxy_creds);
322         if (code != 0) {
323                 krb5_free_principal(ctx, whitelist_principal);
324                 krb5_free_principal(ctx, blacklist_principal);
325                 return code;
326         }
327
328         /*
329          * It's important to store the principal the KDC
330          * returned, as otherwise the caller would not find
331          * the S4U2Self ticket in the krb5_ccache lookup.
332          */
333         store_principal = store_creds.client;
334
335  store:
336         if (blacklist_principal &&
337             krb5_principal_compare(ctx, store_creds.client, blacklist_principal)) {
338                 char *sp = NULL;
339                 char *ip = NULL;
340
341                 code = krb5_unparse_name(ctx, blacklist_principal, &sp);
342                 if (code != 0) {
343                         sp = NULL;
344                 }
345                 code = krb5_unparse_name(ctx, impersonate_principal, &ip);
346                 if (code != 0) {
347                         ip = NULL;
348                 }
349                 DEBUG(1, ("kerberos_kinit_password_cc: "
350                           "KDC returned self principal[%s] while impersonating [%s]\n",
351                           sp?sp:"<no memory>",
352                           ip?ip:"<no memory>"));
353
354                 SAFE_FREE(sp);
355                 SAFE_FREE(ip);
356
357                 krb5_free_principal(ctx, whitelist_principal);
358                 krb5_free_principal(ctx, blacklist_principal);
359                 krb5_free_cred_contents(ctx, &store_creds);
360                 return KRB5_FWD_BAD_PRINCIPAL;
361         }
362         if (blacklist_principal) {
363                 krb5_free_principal(ctx, blacklist_principal);
364         }
365
366         if (whitelist_principal &&
367             !krb5_principal_compare(ctx, store_creds.client, whitelist_principal)) {
368                 char *sp = NULL;
369                 char *ep = NULL;
370
371                 code = krb5_unparse_name(ctx, store_creds.client, &sp);
372                 if (code != 0) {
373                         sp = NULL;
374                 }
375                 code = krb5_unparse_name(ctx, whitelist_principal, &ep);
376                 if (code != 0) {
377                         ep = NULL;
378                 }
379                 DEBUG(1, ("kerberos_kinit_password_cc: "
380                           "KDC returned wrong principal[%s] we expected [%s]\n",
381                           sp?sp:"<no memory>",
382                           ep?ep:"<no memory>"));
383
384                 SAFE_FREE(sp);
385                 SAFE_FREE(ep);
386
387                 krb5_free_principal(ctx, whitelist_principal);
388                 krb5_free_cred_contents(ctx, &store_creds);
389                 return KRB5_FWD_BAD_PRINCIPAL;
390         }
391         if (whitelist_principal) {
392                 krb5_free_principal(ctx, whitelist_principal);
393         }
394
395         code = krb5_cc_initialize(ctx, store_cc, store_principal);
396         if (code != 0) {
397                 krb5_free_cred_contents(ctx, &store_creds);
398                 return code;
399         }
400
401         code = krb5_cc_store_cred(ctx, store_cc, &store_creds);
402         if (code != 0) {
403                 krb5_free_cred_contents(ctx, &store_creds);
404                 return code;
405         }
406
407         if (expire_time) {
408                 *expire_time = (time_t) store_creds.times.endtime;
409         }
410
411         if (kdc_time) {
412                 *kdc_time = (time_t) store_creds.times.starttime;
413         }
414
415         krb5_free_cred_contents(ctx, &store_creds);
416
417         return 0;
418 }
419
420
421 #endif