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 532