]> CyberLeo.Net >> Repos - FreeBSD/releng/9.2.git/blob - crypto/heimdal/kcm/acquire.c
- Copy stable/9 to releng/9.2 as part of the 9.2-RELEASE cycle.
[FreeBSD/releng/9.2.git] / crypto / heimdal / kcm / acquire.c
1 /*
2  * Copyright (c) 2005, PADL Software Pty Ltd.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * 3. Neither the name of PADL Software nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY PADL SOFTWARE AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL PADL SOFTWARE OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32
33 #include "kcm_locl.h"
34
35 RCSID("$Id: acquire.c 22118 2007-12-03 21:44:00Z lha $");
36
37 static krb5_error_code
38 change_pw_and_update_keytab(krb5_context context, kcm_ccache ccache);
39
40 /*
41  * Get a new ticket using a keytab/cached key and swap it into
42  * an existing redentials cache
43  */
44
45 krb5_error_code
46 kcm_ccache_acquire(krb5_context context,
47                    kcm_ccache ccache,
48                    krb5_creds **credp)
49 {
50     krb5_error_code ret = 0;
51     krb5_creds cred;
52     krb5_const_realm realm;
53     krb5_get_init_creds_opt opt;
54     krb5_ccache_data ccdata;
55     char *in_tkt_service = NULL;
56     int done = 0;
57
58     memset(&cred, 0, sizeof(cred));
59
60     KCM_ASSERT_VALID(ccache);
61
62     /* We need a cached key or keytab to acquire credentials */
63     if (ccache->flags & KCM_FLAGS_USE_CACHED_KEY) {
64         if (ccache->key.keyblock.keyvalue.length == 0)
65             krb5_abortx(context,
66                         "kcm_ccache_acquire: KCM_FLAGS_USE_CACHED_KEY without key");
67     } else if (ccache->flags & KCM_FLAGS_USE_KEYTAB) {
68         if (ccache->key.keytab == NULL)
69             krb5_abortx(context,
70                         "kcm_ccache_acquire: KCM_FLAGS_USE_KEYTAB without keytab");
71     } else {
72         kcm_log(0, "Cannot acquire initial credentials for cache %s without key",
73                 ccache->name);
74         return KRB5_FCC_INTERNAL;
75     }
76         
77     HEIMDAL_MUTEX_lock(&ccache->mutex);
78
79     /* Fake up an internal ccache */
80     kcm_internal_ccache(context, ccache, &ccdata);
81
82     /* Now, actually acquire the creds */
83     if (ccache->server != NULL) {
84         ret = krb5_unparse_name(context, ccache->server, &in_tkt_service);
85         if (ret) {
86             kcm_log(0, "Failed to unparse service principal name for cache %s: %s",
87                     ccache->name, krb5_get_err_text(context, ret));
88             return ret;
89         }
90     }
91
92     realm = krb5_principal_get_realm(context, ccache->client);
93
94     krb5_get_init_creds_opt_init(&opt);
95     krb5_get_init_creds_opt_set_default_flags(context, "kcm", realm, &opt);
96     if (ccache->tkt_life != 0)
97         krb5_get_init_creds_opt_set_tkt_life(&opt, ccache->tkt_life);
98     if (ccache->renew_life != 0)
99         krb5_get_init_creds_opt_set_renew_life(&opt, ccache->renew_life);
100
101     if (ccache->flags & KCM_FLAGS_USE_CACHED_KEY) {
102         ret = krb5_get_init_creds_keyblock(context,
103                                            &cred,
104                                            ccache->client,
105                                            &ccache->key.keyblock,
106                                            0,
107                                            in_tkt_service,
108                                            &opt);
109     } else {
110         /* loosely based on lib/krb5/init_creds_pw.c */
111         while (!done) {
112             ret = krb5_get_init_creds_keytab(context,
113                                              &cred,
114                                              ccache->client,
115                                              ccache->key.keytab,
116                                              0,
117                                              in_tkt_service,
118                                              &opt);
119             switch (ret) {
120             case KRB5KDC_ERR_KEY_EXPIRED:
121                 if (in_tkt_service != NULL &&
122                     strcmp(in_tkt_service, "kadmin/changepw") == 0) {
123                     goto out;
124                 }
125
126                 ret = change_pw_and_update_keytab(context, ccache);
127                 if (ret)
128                     goto out;
129                 break;
130             case 0:
131             default:
132                 done = 1;
133                 break;
134             }
135         }
136     }
137
138     if (ret) {
139         kcm_log(0, "Failed to acquire credentials for cache %s: %s",
140                 ccache->name, krb5_get_err_text(context, ret));
141         if (in_tkt_service != NULL)
142             free(in_tkt_service);
143         goto out;
144     }
145
146     if (in_tkt_service != NULL)
147         free(in_tkt_service);
148
149     /* Swap them in */
150     kcm_ccache_remove_creds_internal(context, ccache);
151
152     ret = kcm_ccache_store_cred_internal(context, ccache, &cred, 0, credp);
153     if (ret) {
154         kcm_log(0, "Failed to store credentials for cache %s: %s",
155                 ccache->name, krb5_get_err_text(context, ret));
156         krb5_free_cred_contents(context, &cred);
157         goto out;
158     }
159
160 out:
161     HEIMDAL_MUTEX_unlock(&ccache->mutex);
162
163     return ret;
164 }
165
166 static krb5_error_code
167 change_pw(krb5_context context,
168           kcm_ccache ccache,
169           char *cpn,
170           char *newpw)
171 {
172     krb5_error_code ret;
173     krb5_creds cpw_cred;
174     int result_code;
175     krb5_data result_code_string;
176     krb5_data result_string;
177     krb5_get_init_creds_opt options;
178
179     memset(&cpw_cred, 0, sizeof(cpw_cred));
180
181     krb5_get_init_creds_opt_init(&options);
182     krb5_get_init_creds_opt_set_tkt_life(&options, 60);
183     krb5_get_init_creds_opt_set_forwardable(&options, FALSE);
184     krb5_get_init_creds_opt_set_proxiable(&options, FALSE);
185
186     krb5_data_zero(&result_code_string);
187     krb5_data_zero(&result_string);
188
189     ret = krb5_get_init_creds_keytab(context,
190                                      &cpw_cred,
191                                      ccache->client,
192                                      ccache->key.keytab,
193                                      0,
194                                      "kadmin/changepw",
195                                      &options);
196     if (ret) {
197         kcm_log(0, "Failed to acquire password change credentials "
198                 "for principal %s: %s", 
199                 cpn, krb5_get_err_text(context, ret));
200         goto out;
201     }
202
203     ret = krb5_set_password(context,
204                             &cpw_cred,
205                             newpw,
206                             ccache->client,
207                             &result_code,
208                             &result_code_string,
209                             &result_string);
210     if (ret) {
211         kcm_log(0, "Failed to change password for principal %s: %s",
212                 cpn, krb5_get_err_text(context, ret));
213         goto out;
214     }
215
216     if (result_code) {
217         kcm_log(0, "Failed to change password for principal %s: %.*s",
218                 cpn,
219                 (int)result_string.length,
220                 result_string.length > 0 ? (char *)result_string.data : "");
221         goto out;
222     }
223
224 out:
225     krb5_data_free(&result_string);
226     krb5_data_free(&result_code_string);
227     krb5_free_cred_contents(context, &cpw_cred);
228
229     return ret;
230 }
231
232 struct kcm_keyseed_data {
233     krb5_salt salt;
234     const char *password;
235 };
236
237 static krb5_error_code
238 kcm_password_key_proc(krb5_context context,
239                       krb5_enctype etype,
240                       krb5_salt salt,
241                       krb5_const_pointer keyseed,
242                       krb5_keyblock **key)
243 {
244     krb5_error_code ret;
245     struct kcm_keyseed_data *s = (struct kcm_keyseed_data *)keyseed;
246
247     /* we may be called multiple times */
248     krb5_free_salt(context, s->salt);
249     krb5_data_zero(&s->salt.saltvalue);
250
251     /* stash the salt */
252     s->salt.salttype = salt.salttype;
253
254     ret = krb5_data_copy(&s->salt.saltvalue,
255                          salt.saltvalue.data,
256                          salt.saltvalue.length);
257     if (ret)
258         return ret;
259
260     *key = (krb5_keyblock *)malloc(sizeof(**key));
261     if (*key == NULL) {
262         return ENOMEM;
263     }
264
265     ret = krb5_string_to_key_salt(context, etype, s->password,
266                                   s->salt, *key);
267     if (ret) {
268         free(*key);
269         *key = NULL;
270     }
271
272     return ret;
273 }
274
275 static krb5_error_code
276 get_salt_and_kvno(krb5_context context,
277                   kcm_ccache ccache,
278                   krb5_enctype *etypes,
279                   char *cpn,
280                   char *newpw,
281                   krb5_salt *salt,
282                   unsigned *kvno)
283 {
284     krb5_error_code ret;
285     krb5_creds creds;
286     krb5_ccache_data ccdata;
287     krb5_flags options = 0;
288     krb5_kdc_rep reply;
289     struct kcm_keyseed_data s;
290
291     memset(&creds, 0, sizeof(creds));
292     memset(&reply, 0, sizeof(reply));
293
294     s.password = NULL;
295     s.salt.salttype = (int)ETYPE_NULL;
296     krb5_data_zero(&s.salt.saltvalue);
297
298     *kvno = 0;
299     kcm_internal_ccache(context, ccache, &ccdata);
300     s.password = newpw;
301
302     /* Do an AS-REQ to determine salt and key version number */
303     ret = krb5_copy_principal(context, ccache->client, &creds.client);
304     if (ret)
305         return ret;
306
307     /* Yes, get a ticket to ourselves */
308     ret = krb5_copy_principal(context, ccache->client, &creds.server);
309     if (ret) {
310         krb5_free_principal(context, creds.client);
311         return ret;
312     }
313         
314     ret = krb5_get_in_tkt(context,
315                           options,
316                           NULL,
317                           etypes,
318                           NULL,
319                           kcm_password_key_proc,
320                           &s,
321                           NULL,
322                           NULL,
323                           &creds,
324                           &ccdata,
325                           &reply);
326     if (ret) {
327         kcm_log(0, "Failed to get self ticket for principal %s: %s",
328                 cpn, krb5_get_err_text(context, ret));
329         krb5_free_salt(context, s.salt);
330     } else {
331         *salt = s.salt; /* retrieve stashed salt */
332         if (reply.kdc_rep.enc_part.kvno != NULL)
333             *kvno = *(reply.kdc_rep.enc_part.kvno);
334     }
335     /* ccache may have been modified but it will get trashed anyway */
336
337     krb5_free_cred_contents(context, &creds);
338     krb5_free_kdc_rep(context, &reply);
339
340     return ret;
341 }
342
343 static krb5_error_code
344 update_keytab_entry(krb5_context context,
345                     kcm_ccache ccache,
346                     krb5_enctype etype,
347                     char *cpn,
348                     char *spn,
349                     char *newpw,
350                     krb5_salt salt,
351                     unsigned kvno)
352 {
353     krb5_error_code ret;
354     krb5_keytab_entry entry;
355     krb5_data pw;
356
357     memset(&entry, 0, sizeof(entry));
358
359     pw.data = (char *)newpw;
360     pw.length = strlen(newpw);
361
362     ret = krb5_string_to_key_data_salt(context, etype, pw,
363                                        salt, &entry.keyblock);
364     if (ret) {
365         kcm_log(0, "String to key conversion failed for principal %s "
366                 "and etype %d: %s",
367                 cpn, etype, krb5_get_err_text(context, ret)); 
368         return ret;
369     }
370
371     if (spn == NULL) {
372         ret = krb5_copy_principal(context, ccache->client,
373                                   &entry.principal);
374         if (ret) {
375             kcm_log(0, "Failed to copy principal name %s: %s",
376                     cpn, krb5_get_err_text(context, ret));
377             return ret;
378         }
379     } else {
380         ret = krb5_parse_name(context, spn, &entry.principal);
381         if (ret) {
382             kcm_log(0, "Failed to parse SPN alias %s: %s",
383                     spn, krb5_get_err_text(context, ret));
384             return ret;
385         }
386     }
387
388     entry.vno = kvno;
389     entry.timestamp = time(NULL);
390
391     ret = krb5_kt_add_entry(context, ccache->key.keytab, &entry);
392     if (ret) {
393         kcm_log(0, "Failed to update keytab for principal %s "
394                 "and etype %d: %s",
395                 cpn, etype, krb5_get_err_text(context, ret));
396     }
397
398     krb5_kt_free_entry(context, &entry);
399
400     return ret; 
401 }
402
403 static krb5_error_code
404 update_keytab_entries(krb5_context context,
405                       kcm_ccache ccache,
406                       krb5_enctype *etypes,
407                       char *cpn,
408                       char *spn,
409                       char *newpw,
410                       krb5_salt salt,
411                       unsigned kvno)
412 {
413     krb5_error_code ret = 0;
414     int i;
415
416     for (i = 0; etypes[i] != ETYPE_NULL; i++) {
417         ret = update_keytab_entry(context, ccache, etypes[i],
418                                   cpn, spn, newpw, salt, kvno);
419         if (ret)
420             break;
421     }
422
423     return ret;
424 }
425
426 static void
427 generate_random_pw(krb5_context context,
428                    char *buf,
429                    size_t bufsiz)
430 {
431     unsigned char x[512], *p;
432     size_t i;
433
434     memset(x, 0, sizeof(x));
435     krb5_generate_random_block(x, sizeof(x));
436     p = x;
437
438     for (i = 0; i < bufsiz; i++) {
439         while (isprint(*p) == 0)
440             p++;
441
442         if (p - x >= sizeof(x)) {
443             krb5_generate_random_block(x, sizeof(x));
444             p = x;
445         }
446         buf[i] = (char)*p++;
447     }
448     buf[bufsiz - 1] = '\0';
449     memset(x, 0, sizeof(x));
450 }
451
452 static krb5_error_code
453 change_pw_and_update_keytab(krb5_context context,
454                             kcm_ccache ccache)
455 {
456     char newpw[121];
457     krb5_error_code ret;
458     unsigned kvno;
459     krb5_salt salt;
460     krb5_enctype *etypes = NULL;
461     int i;
462     char *cpn = NULL;
463     char **spns = NULL;
464
465     krb5_data_zero(&salt.saltvalue);
466
467     ret = krb5_unparse_name(context, ccache->client, &cpn);
468     if (ret) {
469         kcm_log(0, "Failed to unparse name: %s",
470                 krb5_get_err_text(context, ret));
471         goto out;
472     }
473
474     ret = krb5_get_default_in_tkt_etypes(context, &etypes);
475     if (ret) {
476         kcm_log(0, "Failed to determine default encryption types: %s",
477                 krb5_get_err_text(context, ret));
478         goto out;
479     }
480
481     /* Generate a random password (there is no set keys protocol) */
482     generate_random_pw(context, newpw, sizeof(newpw));
483
484     /* Change it */
485     ret = change_pw(context, ccache, cpn, newpw);
486     if (ret)
487         goto out;
488
489     /* Do an AS-REQ to determine salt and key version number */
490     ret = get_salt_and_kvno(context, ccache, etypes, cpn, newpw,
491                             &salt, &kvno);
492     if (ret) {
493         kcm_log(0, "Failed to determine salting principal for principal %s: %s",
494                 cpn, krb5_get_err_text(context, ret));
495         goto out;
496     }
497
498     /* Add canonical name */
499     ret = update_keytab_entries(context, ccache, etypes, cpn,
500                                 NULL, newpw, salt, kvno);
501     if (ret)
502         goto out;
503
504     /* Add SPN aliases, if any */
505     spns = krb5_config_get_strings(context, NULL, "kcm",
506                                    "system_ccache", "spn_aliases", NULL);
507     if (spns != NULL) {
508         for (i = 0; spns[i] != NULL; i++) {
509             ret = update_keytab_entries(context, ccache, etypes, cpn,
510                                         spns[i], newpw, salt, kvno);
511             if (ret)
512                 goto out;
513         }
514     }
515
516     kcm_log(0, "Changed expired password for principal %s in cache %s",
517             cpn, ccache->name);
518
519 out:
520     if (cpn != NULL)
521         free(cpn);
522     if (spns != NULL)
523         krb5_config_free_strings(spns);
524     if (etypes != NULL)
525         free(etypes);
526     krb5_free_salt(context, salt);
527     memset(newpw, 0, sizeof(newpw));
528
529     return ret;
530 }
531