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
kt_open(krb5_context ctx,krb5_keytab * kt)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
kt_add_entry(krb5_context ctx,krb5_keytab kt,const krb5_principal princ,const krb5_principal svc_princ,krb5_enctype enctype,krb5_kvno kvno,const char * pw)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
krb5_kt_add_ad_entries(krb5_context ctx,char ** sprincs_str,char * domain,krb5_kvno kvno,uint_t flags,char * password)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
kt_remove_by_key(krb5_context ctx,char * key,uint_t type)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
krb5_kt_remove_by_realm(krb5_context ctx,char * realm)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
krb5_kt_remove_by_svcprinc(krb5_context ctx,char * sprinc_str)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
krb5_kt_ad_validate(krb5_context ctx,char * sprinc_str,uint_t flags,boolean_t * valid)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