s4:auth/kerberos: protect kerberos_kinit_password_cc() against old KDCs
[Samba/gebeck_regimport.git] / source4 / auth / kerberos / kerberos.c
blob0fc9d143abdbe00a92ad4568cbab635f25c0d7fc
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
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.
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.
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/>.
24 #include "includes.h"
25 #include "system/kerberos.h"
26 #include "auth/kerberos/kerberos.h"
28 #ifdef HAVE_KRB5
31 simulate a kinit, putting the tgt in the given credentials cache.
32 Orignally by remus@snapserver.com
34 This version is built to use a keyblock, rather than needing the
35 original password.
37 The impersonate_principal is the principal if NULL, or the principal to impersonate
39 The target_service defaults to the krbtgt if NULL, but could be kpasswd/realm or the local service (if we are doing s4u2self)
41 krb5_error_code kerberos_kinit_keyblock_cc(krb5_context ctx, krb5_ccache cc,
42 krb5_principal principal, krb5_keyblock *keyblock,
43 const char *target_service,
44 krb5_get_init_creds_opt *krb_options,
45 time_t *expire_time, time_t *kdc_time)
47 krb5_error_code code = 0;
48 krb5_creds my_creds;
50 if ((code = krb5_get_init_creds_keyblock(ctx, &my_creds, principal, keyblock,
51 0, target_service, krb_options))) {
52 return code;
55 if ((code = krb5_cc_initialize(ctx, cc, principal))) {
56 krb5_free_cred_contents(ctx, &my_creds);
57 return code;
60 if ((code = krb5_cc_store_cred(ctx, cc, &my_creds))) {
61 krb5_free_cred_contents(ctx, &my_creds);
62 return code;
65 if (expire_time) {
66 *expire_time = (time_t) my_creds.times.endtime;
69 if (kdc_time) {
70 *kdc_time = (time_t) my_creds.times.starttime;
73 krb5_free_cred_contents(ctx, &my_creds);
75 return 0;
79 simulate a kinit, putting the tgt in the given credentials cache.
80 Orignally by remus@snapserver.com
82 The impersonate_principal is the principal if NULL, or the principal to impersonate
84 The self_service, should be the local service (for S4U2Self if impersonate_principal is given).
86 The target_service defaults to the krbtgt if NULL, but could be kpasswd/realm or a remote service (for S4U2Proxy)
89 krb5_error_code kerberos_kinit_password_cc(krb5_context ctx, krb5_ccache store_cc,
90 krb5_principal init_principal,
91 const char *init_password,
92 krb5_principal impersonate_principal,
93 const char *self_service,
94 const char *target_service,
95 krb5_get_init_creds_opt *krb_options,
96 time_t *expire_time, time_t *kdc_time)
98 krb5_error_code code = 0;
99 krb5_get_creds_opt options;
100 krb5_principal store_principal;
101 krb5_creds store_creds;
102 krb5_creds *s4u2self_creds;
103 Ticket s4u2self_ticket;
104 size_t s4u2self_ticketlen;
105 krb5_creds *s4u2proxy_creds;
106 krb5_principal self_princ;
107 bool s4u2proxy;
108 krb5_principal target_princ;
109 krb5_ccache tmp_cc;
110 const char *self_realm;
111 krb5_principal blacklist_principal = NULL;
112 krb5_principal whitelist_principal = NULL;
114 if (impersonate_principal && self_service == NULL) {
115 return EINVAL;
119 * If we are not impersonating, then get this ticket for the
120 * target service, otherwise a krbtgt, and get the next ticket
121 * for the target
123 code = krb5_get_init_creds_password(ctx, &store_creds,
124 init_principal,
125 init_password,
126 NULL, NULL,
128 impersonate_principal ? NULL : target_service,
129 krb_options);
130 if (code != 0) {
131 return code;
134 store_principal = init_principal;
136 if (impersonate_principal == NULL) {
137 goto store;
141 * We are trying S4U2Self now:
143 * As we do not want to expose our TGT in the
144 * krb5_ccache, which is also holds the impersonated creds.
146 * Some low level krb5/gssapi function might use the TGT
147 * identity and let the client act as our machine account.
149 * We need to avoid that and use a temporary krb5_ccache
150 * in order to pass our TGT to the krb5_get_creds() function.
152 code = krb5_cc_new_unique(ctx, NULL, NULL, &tmp_cc);
153 if (code != 0) {
154 krb5_free_cred_contents(ctx, &store_creds);
155 return code;
158 code = krb5_cc_initialize(ctx, tmp_cc, store_creds.client);
159 if (code != 0) {
160 krb5_cc_destroy(ctx, tmp_cc);
161 krb5_free_cred_contents(ctx, &store_creds);
162 return code;
165 code = krb5_cc_store_cred(ctx, tmp_cc, &store_creds);
166 if (code != 0) {
167 krb5_free_cred_contents(ctx, &store_creds);
168 krb5_cc_destroy(ctx, tmp_cc);
169 return code;
173 * we need to remember the client principal of our
174 * TGT and make sure the KDC does not return this
175 * in the impersonated tickets. This can happen
176 * if the KDC does not support S4U2Self and S4U2Proxy.
178 blacklist_principal = store_creds.client;
179 store_creds.client = NULL;
180 krb5_free_cred_contents(ctx, &store_creds);
183 * Check if we also need S4U2Proxy or if S4U2Self is
184 * enough in order to get a ticket for the target.
186 if (target_service == NULL) {
187 s4u2proxy = false;
188 } else if (strcmp(target_service, self_service) == 0) {
189 s4u2proxy = false;
190 } else {
191 s4u2proxy = true;
195 * For S4U2Self we need our own service principal,
196 * which belongs to our own realm (available on
197 * our client principal).
199 self_realm = krb5_principal_get_realm(ctx, init_principal);
201 code = krb5_parse_name(ctx, self_service, &self_princ);
202 if (code != 0) {
203 krb5_free_principal(ctx, blacklist_principal);
204 krb5_cc_destroy(ctx, tmp_cc);
205 return code;
208 code = krb5_principal_set_realm(ctx, self_princ, self_realm);
209 if (code != 0) {
210 krb5_free_principal(ctx, blacklist_principal);
211 krb5_free_principal(ctx, self_princ);
212 krb5_cc_destroy(ctx, tmp_cc);
213 return code;
216 code = krb5_get_creds_opt_alloc(ctx, &options);
217 if (code != 0) {
218 krb5_free_principal(ctx, blacklist_principal);
219 krb5_free_principal(ctx, self_princ);
220 krb5_cc_destroy(ctx, tmp_cc);
221 return code;
224 if (s4u2proxy) {
226 * If we want S4U2Proxy, we need the forwardable flag
227 * on the S4U2Self ticket.
229 krb5_get_creds_opt_set_options(ctx, options, KRB5_GC_FORWARDABLE);
232 code = krb5_get_creds_opt_set_impersonate(ctx, options,
233 impersonate_principal);
234 if (code != 0) {
235 krb5_get_creds_opt_free(ctx, options);
236 krb5_free_principal(ctx, blacklist_principal);
237 krb5_free_principal(ctx, self_princ);
238 krb5_cc_destroy(ctx, tmp_cc);
239 return code;
242 code = krb5_get_creds(ctx, options, tmp_cc,
243 self_princ, &s4u2self_creds);
244 krb5_get_creds_opt_free(ctx, options);
245 krb5_free_principal(ctx, self_princ);
246 if (code != 0) {
247 krb5_free_principal(ctx, blacklist_principal);
248 krb5_cc_destroy(ctx, tmp_cc);
249 return code;
252 if (!s4u2proxy) {
253 krb5_cc_destroy(ctx, tmp_cc);
256 * Now make sure we store the impersonated principal
257 * and creds instead of the TGT related stuff
258 * in the krb5_ccache of the caller.
260 code = krb5_copy_creds_contents(ctx, s4u2self_creds,
261 &store_creds);
262 krb5_free_creds(ctx, s4u2self_creds);
263 if (code != 0) {
264 return code;
268 * It's important to store the principal the KDC
269 * returned, as otherwise the caller would not find
270 * the S4U2Self ticket in the krb5_ccache lookup.
272 store_principal = store_creds.client;
273 goto store;
277 * We are trying S4U2Proxy:
279 * We need the ticket from the S4U2Self step
280 * and our TGT in order to get the delegated ticket.
282 code = decode_Ticket((const uint8_t *)s4u2self_creds->ticket.data,
283 s4u2self_creds->ticket.length,
284 &s4u2self_ticket,
285 &s4u2self_ticketlen);
286 if (code != 0) {
287 krb5_free_creds(ctx, s4u2self_creds);
288 krb5_free_principal(ctx, blacklist_principal);
289 krb5_cc_destroy(ctx, tmp_cc);
290 return code;
294 * we need to remember the client principal of the
295 * S4U2Self stage and as it needs to match the one we
296 * will get for the S4U2Proxy stage. We need this
297 * in order to detect KDCs which does not support S4U2Proxy.
299 whitelist_principal = s4u2self_creds->client;
300 s4u2self_creds->client = NULL;
301 krb5_free_creds(ctx, s4u2self_creds);
304 * For S4U2Proxy we also got a target service principal,
305 * which also belongs to our own realm (available on
306 * our client principal).
308 code = krb5_parse_name(ctx, target_service, &target_princ);
309 if (code != 0) {
310 free_Ticket(&s4u2self_ticket);
311 krb5_free_principal(ctx, whitelist_principal);
312 krb5_free_principal(ctx, blacklist_principal);
313 krb5_cc_destroy(ctx, tmp_cc);
314 return code;
317 code = krb5_principal_set_realm(ctx, target_princ, self_realm);
318 if (code != 0) {
319 free_Ticket(&s4u2self_ticket);
320 krb5_free_principal(ctx, target_princ);
321 krb5_free_principal(ctx, whitelist_principal);
322 krb5_free_principal(ctx, blacklist_principal);
323 krb5_cc_destroy(ctx, tmp_cc);
324 return code;
327 code = krb5_get_creds_opt_alloc(ctx, &options);
328 if (code != 0) {
329 free_Ticket(&s4u2self_ticket);
330 krb5_free_principal(ctx, target_princ);
331 krb5_free_principal(ctx, whitelist_principal);
332 krb5_free_principal(ctx, blacklist_principal);
333 krb5_cc_destroy(ctx, tmp_cc);
334 return code;
337 krb5_get_creds_opt_set_options(ctx, options, KRB5_GC_FORWARDABLE);
338 krb5_get_creds_opt_set_options(ctx, options, KRB5_GC_CONSTRAINED_DELEGATION);
340 code = krb5_get_creds_opt_set_ticket(ctx, options, &s4u2self_ticket);
341 free_Ticket(&s4u2self_ticket);
342 if (code != 0) {
343 krb5_get_creds_opt_free(ctx, options);
344 krb5_free_principal(ctx, target_princ);
345 krb5_free_principal(ctx, whitelist_principal);
346 krb5_free_principal(ctx, blacklist_principal);
347 krb5_cc_destroy(ctx, tmp_cc);
348 return code;
351 code = krb5_get_creds(ctx, options, tmp_cc,
352 target_princ, &s4u2proxy_creds);
353 krb5_get_creds_opt_free(ctx, options);
354 krb5_free_principal(ctx, target_princ);
355 krb5_cc_destroy(ctx, tmp_cc);
356 if (code != 0) {
357 krb5_free_principal(ctx, whitelist_principal);
358 krb5_free_principal(ctx, blacklist_principal);
359 return code;
363 * Now make sure we store the impersonated principal
364 * and creds instead of the TGT related stuff
365 * in the krb5_ccache of the caller.
367 code = krb5_copy_creds_contents(ctx, s4u2proxy_creds,
368 &store_creds);
369 krb5_free_creds(ctx, s4u2proxy_creds);
370 if (code != 0) {
371 krb5_free_principal(ctx, whitelist_principal);
372 krb5_free_principal(ctx, blacklist_principal);
373 return code;
377 * It's important to store the principal the KDC
378 * returned, as otherwise the caller would not find
379 * the S4U2Self ticket in the krb5_ccache lookup.
381 store_principal = store_creds.client;
383 store:
384 if (blacklist_principal &&
385 krb5_principal_compare(ctx, store_creds.client, blacklist_principal)) {
386 char *sp = NULL;
387 char *ip = NULL;
389 code = krb5_unparse_name(ctx, blacklist_principal, &sp);
390 if (code != 0) {
391 sp = NULL;
393 code = krb5_unparse_name(ctx, impersonate_principal, &ip);
394 if (code != 0) {
395 ip = NULL;
397 DEBUG(1, ("kerberos_kinit_password_cc: "
398 "KDC returned self principal[%s] while impersonating [%s]\n",
399 sp?sp:"<no memory>",
400 ip?ip:"<no memory>"));
402 SAFE_FREE(sp);
403 SAFE_FREE(ip);
405 krb5_free_principal(ctx, whitelist_principal);
406 krb5_free_principal(ctx, blacklist_principal);
407 krb5_free_cred_contents(ctx, &store_creds);
408 return KRB5_FWD_BAD_PRINCIPAL;
410 if (blacklist_principal) {
411 krb5_free_principal(ctx, blacklist_principal);
414 if (whitelist_principal &&
415 !krb5_principal_compare(ctx, store_creds.client, whitelist_principal)) {
416 char *sp = NULL;
417 char *ep = NULL;
419 code = krb5_unparse_name(ctx, store_creds.client, &sp);
420 if (code != 0) {
421 sp = NULL;
423 code = krb5_unparse_name(ctx, whitelist_principal, &ep);
424 if (code != 0) {
425 ep = NULL;
427 DEBUG(1, ("kerberos_kinit_password_cc: "
428 "KDC returned wrong principal[%s] we expected [%s]\n",
429 sp?sp:"<no memory>",
430 ep?ep:"<no memory>"));
432 SAFE_FREE(sp);
433 SAFE_FREE(ep);
435 krb5_free_principal(ctx, whitelist_principal);
436 krb5_free_cred_contents(ctx, &store_creds);
437 return KRB5_FWD_BAD_PRINCIPAL;
439 if (whitelist_principal) {
440 krb5_free_principal(ctx, whitelist_principal);
443 code = krb5_cc_initialize(ctx, store_cc, store_principal);
444 if (code != 0) {
445 krb5_free_cred_contents(ctx, &store_creds);
446 return code;
449 code = krb5_cc_store_cred(ctx, store_cc, &store_creds);
450 if (code != 0) {
451 krb5_free_cred_contents(ctx, &store_creds);
452 return code;
455 if (expire_time) {
456 *expire_time = (time_t) store_creds.times.endtime;
459 if (kdc_time) {
460 *kdc_time = (time_t) store_creds.times.starttime;
463 krb5_free_cred_contents(ctx, &store_creds);
465 return 0;
469 #endif