1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. 24 */ 25 26 #include <krb5.h> 27 #include <errno.h> 28 #include <netdb.h> 29 #include <strings.h> 30 #include <stdio.h> 31 #include <assert.h> 32 #include <ctype.h> 33 #include "kt_solaris.h" 34 35 #define AES128 ENCTYPE_AES128_CTS_HMAC_SHA1_96 36 #define AES256 ENCTYPE_AES256_CTS_HMAC_SHA1_96 37 #define DES3 ENCTYPE_DES3_CBC_SHA1 38 #define AES_ENTRIES 2 39 #define HOST_TRUNC 15 40 #define SVC_ENTRIES 4 41 42 static krb5_error_code 43 kt_open(krb5_context ctx, krb5_keytab *kt) 44 { 45 krb5_error_code code; 46 char buf[MAX_KEYTAB_NAME_LEN], ktstr[MAX_KEYTAB_NAME_LEN]; 47 48 memset(buf, 0, sizeof (buf)); 49 memset(ktstr, 0, sizeof (ktstr)); 50 51 if ((code = krb5_kt_default_name(ctx, buf, sizeof (buf))) != 0) 52 return (code); 53 54 /* 55 * The default is file type w/o the write. If it's anything besides 56 * FILE or WRFILE then we bail as quickly as possible. 57 */ 58 if (strncmp(buf, "FILE:", strlen("FILE:")) == 0) 59 (void) snprintf(ktstr, sizeof (ktstr), "WR%s", buf); 60 else if (strncmp(buf, "WRFILE:", strlen("WRFILE:")) == 0) 61 (void) snprintf(ktstr, sizeof (ktstr), "%s", buf); 62 else 63 return (EINVAL); 64 65 return (krb5_kt_resolve(ctx, ktstr, kt)); 66 } 67 68 static krb5_error_code 69 kt_add_entry(krb5_context ctx, krb5_keytab kt, const krb5_principal princ, 70 const krb5_principal svc_princ, krb5_enctype enctype, krb5_kvno kvno, 71 const char *pw) 72 { 73 krb5_keytab_entry entry; 74 krb5_data password, salt; 75 krb5_keyblock key; 76 krb5_error_code code; 77 78 memset(&entry, 0, sizeof (entry)); 79 memset(&key, 0, sizeof (krb5_keyblock)); 80 81 password.length = strlen(pw); 82 password.data = (char *)pw; 83 84 if ((code = krb5_principal2salt(ctx, svc_princ, &salt)) != 0) { 85 return (code); 86 } 87 88 if ((krb5_c_string_to_key(ctx, enctype, &password, &salt, &key)) != 0) 89 goto cleanup; 90 91 entry.key = key; 92 entry.vno = kvno; 93 entry.principal = princ; 94 95 code = krb5_kt_add_entry(ctx, kt, &entry); 96 97 cleanup: 98 99 krb5_xfree(salt.data); 100 krb5_free_keyblock_contents(ctx, &key); 101 102 return (code); 103 } 104 105 /* 106 * krb5_error_code krb5_kt_add_ad_entries(krb5_context ctx, char **sprincs_str, 107 * krb5_kvno kvno, uint_t flags, char *password) 108 * 109 * Adds keys to the keytab file for a default set of service principals in an 110 * Active Directory environment. 111 * 112 * where ctx is the pointer passed back from krb5_init_context 113 * where sprincs_str is an array of service principal names to be added 114 * to the keytab file, terminated by a NULL pointer 115 * where domain is the domain used to fully qualify the hostname for 116 * constructing the salt in the string-to-key function. 117 * where kvno is the key version number of the set of service principal 118 * keys to be added 119 * where flags is the set of conditions that affects the key table entries 120 * current set of defined flags: 121 * 122 * encryption type 123 * --------------- 124 * 0x00000001 KRB5_KT_FLAG_AES_SUPPORT (core set + AES-256-128 keys added) 125 * 126 * where password is the password that will be used to derive the key for 127 * the associated service principals in the keytab file 128 * 129 * Note: this function is used for adding service principals to the 130 * local /etc/krb5/krb5.keytab (unless KRB5_KTNAME has been set to something 131 * different, see krb5envvar(7)) file when the client belongs to an AD domain. 132 * The keytab file is populated differently for an AD domain as the various 133 * service principals share the same key material, unlike MIT based 134 * implementations. 135 * 136 * Note: For encryption types; the union of the enc type flag and the 137 * capabilities of the client is used to determine the enc type set to 138 * populate the keytab file. 139 * 140 * Note: The keys are not created for any AES enctypes UNLESS the 141 * KRB5_KT_FLAG_AES_SUPPORT flag is set and permitted_enctypes has the AES 142 * enctypes enabled. 143 * 144 * Note: In Active Directory environments the salt is constructed by truncating 145 * the host name to 15 characters and only use the host svc princ as the salt, 146 * e.g. host/<str15>.<domain>@<realm>. The realm name is determined by parsing 147 * sprincs_str. The local host name to construct is determined by calling 148 * gethostname(3C). If AD environments construct salts differently in the 149 * future or this function is expanded outside of AD environments one could 150 * derive the salt by sending an initial authentication exchange. 151 * 152 * Note: The kvno was previously determined by performing an LDAP query of the 153 * computer account's msDS-KeyVersionNumber attribute. If the schema changes 154 * in the future or this function is expanded outside of AD environments then 155 * one could derive the principal's kvno by requesting a service ticket. 156 */ 157 krb5_error_code 158 krb5_kt_add_ad_entries(krb5_context ctx, char **sprincs_str, char *domain, 159 krb5_kvno kvno, uint_t flags, char *password) 160 { 161 krb5_principal princ = NULL, salt = NULL, f_princ = NULL; 162 krb5_keytab kt = NULL; 163 krb5_enctype *enctypes = NULL, *tenctype, penctype = 0; 164 char **tprinc, *ptr, *token, *t_host = NULL, *realm; 165 char localname[MAXHOSTNAMELEN]; 166 krb5_error_code code; 167 krb5_boolean similar; 168 uint_t t_len; 169 170 assert(ctx != NULL && sprincs_str != NULL && *sprincs_str != NULL); 171 assert(password != NULL && domain != NULL); 172 173 if ((code = krb5_parse_name(ctx, *sprincs_str, &f_princ)) != 0) 174 return (code); 175 if (krb5_princ_realm(ctx, f_princ)->length == 0) { 176 code = EINVAL; 177 goto cleanup; 178 } 179 realm = krb5_princ_realm(ctx, f_princ)->data; 180 181 if (gethostname(localname, MAXHOSTNAMELEN) != 0) { 182 code = errno; 183 goto cleanup; 184 } 185 token = localname; 186 187 /* 188 * Local host name could be fully qualified and/or in upper case, but 189 * usually and appropriately not. 190 */ 191 if ((ptr = strchr(token, '.')) != NULL) 192 ptr = '\0'; 193 for (ptr = token; *ptr; ptr++) 194 *ptr = tolower(*ptr); 195 /* 196 * Windows servers currently truncate the host name to 15 characters 197 * and only use the host svc princ as the salt, e.g. 198 * host/str15.domain@realm 199 */ 200 t_len = snprintf(NULL, 0, "host/%.*s.%s@%s", HOST_TRUNC, token, domain, 201 realm) + 1; 202 if ((t_host = malloc(t_len)) == NULL) { 203 code = ENOMEM; 204 goto cleanup; 205 } 206 (void) snprintf(t_host, t_len, "host/%.*s.%s@%s", HOST_TRUNC, token, 207 domain, realm); 208 209 if ((code = krb5_parse_name(ctx, t_host, &salt)) != 0) 210 goto cleanup; 211 212 if ((code = kt_open(ctx, &kt)) != 0) 213 goto cleanup; 214 215 code = krb5_get_permitted_enctypes(ctx, &enctypes); 216 if (code != 0 || *enctypes == 0) 217 goto cleanup; 218 219 for (tprinc = sprincs_str; *tprinc; tprinc++) { 220 221 if ((code = krb5_parse_name(ctx, *tprinc, &princ)) != 0) 222 goto cleanup; 223 224 for (tenctype = enctypes; *tenctype; tenctype++) { 225 if ((!(flags & KRB5_KT_FLAG_AES_SUPPORT) && 226 (*tenctype == AES128 || *tenctype == AES256)) || 227 (*tenctype == DES3)) { 228 continue; 229 } 230 231 if (penctype) { 232 code = krb5_c_enctype_compare(ctx, *tenctype, 233 penctype, &similar); 234 if (code != 0) 235 goto cleanup; 236 else if (similar) 237 continue; 238 } 239 240 code = kt_add_entry(ctx, kt, princ, salt, *tenctype, 241 kvno, password); 242 if (code != 0) 243 goto cleanup; 244 245 penctype = *tenctype; 246 } 247 248 krb5_free_principal(ctx, princ); 249 princ = NULL; 250 } 251 252 cleanup: 253 254 if (f_princ != NULL) 255 krb5_free_principal(ctx, f_princ); 256 if (salt != NULL) 257 krb5_free_principal(ctx, salt); 258 if (t_host != NULL) 259 free(t_host); 260 if (kt != NULL) 261 (void) krb5_kt_close(ctx, kt); 262 if (enctypes != NULL) 263 krb5_free_ktypes(ctx, enctypes); 264 if (princ != NULL) 265 krb5_free_principal(ctx, princ); 266 267 return (code); 268 } 269 270 #define PRINCIPAL 0 271 #define REALM 1 272 273 static krb5_error_code 274 kt_remove_by_key(krb5_context ctx, char *key, uint_t type) 275 { 276 krb5_error_code code; 277 krb5_kt_cursor cursor; 278 krb5_keytab_entry entry; 279 krb5_keytab kt = NULL; 280 krb5_principal svc_princ = NULL; 281 krb5_principal_data realm_data; 282 boolean_t found = FALSE; 283 284 assert(ctx != NULL && key != NULL); 285 286 if (type == REALM) { 287 krb5_princ_realm(ctx, &realm_data)->length = strlen(key); 288 krb5_princ_realm(ctx, &realm_data)->data = key; 289 } else if (type == PRINCIPAL) { 290 if ((code = krb5_parse_name(ctx, key, &svc_princ)) != 0) 291 goto cleanup; 292 } else 293 return (EINVAL); 294 295 if ((code = kt_open(ctx, &kt)) != 0) 296 goto cleanup; 297 298 if ((code = krb5_kt_start_seq_get(ctx, kt, &cursor)) != 0) 299 goto cleanup; 300 301 while ((code = krb5_kt_next_entry(ctx, kt, &entry, &cursor)) == 0) { 302 if (type == PRINCIPAL && krb5_principal_compare(ctx, svc_princ, 303 entry.principal)) { 304 found = TRUE; 305 } else if (type == REALM && krb5_realm_compare(ctx, &realm_data, 306 entry.principal)) { 307 found = TRUE; 308 } 309 310 if (found == TRUE) { 311 code = krb5_kt_end_seq_get(ctx, kt, &cursor); 312 if (code != 0) { 313 krb5_kt_free_entry(ctx, &entry); 314 goto cleanup; 315 } 316 317 code = krb5_kt_remove_entry(ctx, kt, &entry); 318 if (code != 0) { 319 krb5_kt_free_entry(ctx, &entry); 320 goto cleanup; 321 } 322 323 code = krb5_kt_start_seq_get(ctx, kt, &cursor); 324 if (code != 0) { 325 krb5_kt_free_entry(ctx, &entry); 326 goto cleanup; 327 } 328 329 found = FALSE; 330 } 331 332 krb5_kt_free_entry(ctx, &entry); 333 } 334 335 if (code && code != KRB5_KT_END) 336 goto cleanup; 337 338 code = krb5_kt_end_seq_get(ctx, kt, &cursor); 339 340 cleanup: 341 342 if (svc_princ != NULL) 343 krb5_free_principal(ctx, svc_princ); 344 if (kt != NULL) 345 (void) krb5_kt_close(ctx, kt); 346 347 return (code); 348 } 349 350 /* 351 * krb5_error_code krb5_kt_remove_by_realm(krb5_context ctx, char *realm) 352 * 353 * Removes all key entries in the keytab file that match the exact realm name 354 * specified. 355 * 356 * where ctx is the pointer passed back from krb5_init_context 357 * where realm is the realm name that is matched for any keytab entries 358 * to be removed 359 * 360 * Note: if there are no entries matching realm then 0 (success) is returned 361 */ 362 krb5_error_code 363 krb5_kt_remove_by_realm(krb5_context ctx, char *realm) 364 { 365 366 return (kt_remove_by_key(ctx, realm, REALM)); 367 } 368 369 /* 370 * krb5_error_code krb5_kt_remove_by_svcprinc(krb5_context ctx, 371 * char *sprinc_str) 372 * 373 * Removes all key entries in the keytab file that match the exact service 374 * principal name specified. 375 * 376 * where ctx is the pointer passed back from krb5_init_context 377 * where sprinc_str is the service principal name that is matched for any 378 * keytab entries to be removed 379 * 380 * Note: if there are no entries matching sprinc_str then 0 (success) is 381 * returned 382 */ 383 krb5_error_code 384 krb5_kt_remove_by_svcprinc(krb5_context ctx, char *sprinc_str) 385 { 386 387 return (kt_remove_by_key(ctx, sprinc_str, PRINCIPAL)); 388 } 389 390 /* 391 * krb5_error_code krb5_kt_validate(krb5_context ctx, char *sprinc_str, 392 * uint_t flags, boolean_t *valid) 393 * 394 * The validate function determines that the service principal exists and that 395 * it has a valid set of encryption types for said principal. 396 * 397 * where ctx is the pointer passed back from krb5_init_context 398 * where sprinc_str is the principal to be validated in the keytab file 399 * where flags is the set of conditions that affects the key table entries 400 * that the function considers valid 401 * current set of defined flags: 402 * 403 * encryption type 404 * --------------- 405 * 0x00000001 KRB5_KT_FLAG_AES_SUPPORT (core set + AES-256-128 keys are 406 * valid) 407 * 408 * where valid is a boolean that is set if the sprinc_str is correctly 409 * populated in the keytab file based on the flags set else valid is unset. 410 * 411 * Note: The validate function assumes that only one set of keys exists for 412 * a corresponding service principal, of key version number (kvno) n. It would 413 * consider more than one kvno set as invalid. This is from the fact that AD 414 * clients will attempt to refresh credential caches if KRB5KRB_AP_ERR_MODIFIED 415 * is returned by the acceptor when the requested kvno is not found within the 416 * keytab file. 417 */ 418 krb5_error_code 419 krb5_kt_ad_validate(krb5_context ctx, char *sprinc_str, uint_t flags, 420 boolean_t *valid) 421 { 422 krb5_error_code code; 423 krb5_kt_cursor cursor; 424 krb5_keytab_entry entry; 425 krb5_keytab kt = NULL; 426 krb5_principal svc_princ = NULL; 427 krb5_enctype *enctypes, *tenctype, penctype = 0; 428 boolean_t ck_aes = FALSE; 429 uint_t aes_count = 0, kt_entries = 0; 430 krb5_boolean similar; 431 432 assert(ctx != NULL && sprinc_str != NULL && valid != NULL); 433 434 *valid = FALSE; 435 ck_aes = flags & KRB5_KT_FLAG_AES_SUPPORT; 436 437 if ((code = krb5_parse_name(ctx, sprinc_str, &svc_princ)) != 0) 438 goto cleanup; 439 440 if ((code = kt_open(ctx, &kt)) != 0) 441 goto cleanup; 442 443 code = krb5_get_permitted_enctypes(ctx, &enctypes); 444 if (code != 0 || *enctypes == 0) 445 goto cleanup; 446 447 if ((code = krb5_kt_start_seq_get(ctx, kt, &cursor)) != 0) 448 goto cleanup; 449 450 while ((code = krb5_kt_next_entry(ctx, kt, &entry, &cursor)) == 0) { 451 if (krb5_principal_compare(ctx, svc_princ, entry.principal)) { 452 453 for (tenctype = enctypes; *tenctype; tenctype++) { 454 if (penctype) { 455 code = krb5_c_enctype_compare(ctx, 456 *tenctype, penctype, &similar); 457 if (code != 0) { 458 krb5_kt_free_entry(ctx, &entry); 459 goto cleanup; 460 } else if (similar) 461 continue; 462 } 463 464 if ((*tenctype != DES3) && 465 (entry.key.enctype == *tenctype)) { 466 kt_entries++; 467 } 468 469 penctype = *tenctype; 470 } 471 472 if ((entry.key.enctype == AES128) || 473 (entry.key.enctype == AES256)) { 474 aes_count++; 475 } 476 } 477 478 krb5_kt_free_entry(ctx, &entry); 479 } 480 481 if (code && code != KRB5_KT_END) 482 goto cleanup; 483 484 if ((code = krb5_kt_end_seq_get(ctx, kt, &cursor))) 485 goto cleanup; 486 487 if (ck_aes == TRUE) { 488 if ((kt_entries != SVC_ENTRIES) || (aes_count != AES_ENTRIES)) 489 goto cleanup; 490 } else if (kt_entries != (SVC_ENTRIES - AES_ENTRIES)) 491 goto cleanup; 492 493 *valid = TRUE; 494 495 cleanup: 496 497 if (svc_princ != NULL) 498 krb5_free_principal(ctx, svc_princ); 499 if (kt != NULL) 500 (void) krb5_kt_close(ctx, kt); 501 if (enctypes != NULL) 502 krb5_free_ktypes(ctx, enctypes); 503 504 return (code); 505 } 506