xref: /illumos-gate/usr/src/lib/gss_mechs/mech_krb5/krb5/keytab/kt_solaris.c (revision 3580e26c24814e4d892b1eae539b8761388f79f1)
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