Mercurial > illumos > illumos-gate
changeset 13084:8edea3c8c280
6909129 krb5 keytab management API should be simplified to easily merge keys from different realms
author | Shawn Emery <Shawn.Emery@Sun.COM> |
---|---|
date | Wed, 11 Aug 2010 13:08:50 -0600 |
parents | ec9d94ce123b |
children | 6b4a9cc90eaa |
files | usr/src/lib/gss_mechs/mech_krb5/Makefile.com usr/src/lib/gss_mechs/mech_krb5/krb5/keytab/kt_solaris.c usr/src/lib/gss_mechs/mech_krb5/krb5/keytab/kt_solaris.h usr/src/lib/gss_mechs/mech_krb5/mapfile-vers |
diffstat | 4 files changed, 551 insertions(+), 1 deletions(-) [+] |
line wrap: on
line diff
--- a/usr/src/lib/gss_mechs/mech_krb5/Makefile.com Wed Aug 11 11:18:58 2010 -0700 +++ b/usr/src/lib/gss_mechs/mech_krb5/Makefile.com Wed Aug 11 13:08:50 2010 -0600 @@ -115,7 +115,8 @@ # krb5/keytab K5_KT= ktadd.o ktbase.o ktdefault.o ktfr_entry.o \ - ktremove.o read_servi.o kt_file.o kt_srvtab.o ktfns.o kt_findrealm.o + ktremove.o read_servi.o kt_file.o kt_srvtab.o ktfns.o kt_findrealm.o \ + kt_solaris.o K5_KRB= addr_comp.o addr_order.o addr_srch.o \ auth_con.o bld_pr_ext.o bld_princ.o chk_trans.o \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/lib/gss_mechs/mech_krb5/krb5/keytab/kt_solaris.c Wed Aug 11 13:08:50 2010 -0600 @@ -0,0 +1,505 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + */ + +#include <krb5.h> +#include <errno.h> +#include <netdb.h> +#include <strings.h> +#include <stdio.h> +#include <assert.h> +#include <ctype.h> +#include "kt_solaris.h" + +#define AES128 ENCTYPE_AES128_CTS_HMAC_SHA1_96 +#define AES256 ENCTYPE_AES256_CTS_HMAC_SHA1_96 +#define DES3 ENCTYPE_DES3_CBC_SHA1 +#define AES_ENTRIES 2 +#define HOST_TRUNC 15 +#define SVC_ENTRIES 4 + +static krb5_error_code +kt_open(krb5_context ctx, krb5_keytab *kt) +{ + krb5_error_code code; + char buf[MAX_KEYTAB_NAME_LEN], ktstr[MAX_KEYTAB_NAME_LEN]; + + memset(buf, 0, sizeof (buf)); + memset(ktstr, 0, sizeof (ktstr)); + + if ((code = krb5_kt_default_name(ctx, buf, sizeof (buf))) != 0) + return (code); + + /* + * The default is file type w/o the write. If it's anything besides + * FILE or WRFILE then we bail as quickly as possible. + */ + if (strncmp(buf, "FILE:", strlen("FILE:")) == 0) + (void) snprintf(ktstr, sizeof (ktstr), "WR%s", buf); + else if (strncmp(buf, "WRFILE:", strlen("WRFILE:")) == 0) + (void) snprintf(ktstr, sizeof (ktstr), "%s", buf); + else + return (EINVAL); + + return (krb5_kt_resolve(ctx, ktstr, kt)); +} + +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) +{ + krb5_keytab_entry entry; + krb5_data password, salt; + krb5_keyblock key; + krb5_error_code code; + + memset(&entry, 0, sizeof (entry)); + memset(&key, 0, sizeof (krb5_keyblock)); + + password.length = strlen(pw); + password.data = (char *)pw; + + if ((code = krb5_principal2salt(ctx, svc_princ, &salt)) != 0) { + return (code); + } + + if ((krb5_c_string_to_key(ctx, enctype, &password, &salt, &key)) != 0) + goto cleanup; + + entry.key = key; + entry.vno = kvno; + entry.principal = princ; + + code = krb5_kt_add_entry(ctx, kt, &entry); + +cleanup: + + krb5_xfree(salt.data); + krb5_free_keyblock_contents(ctx, &key); + + return (code); +} + +/* + * krb5_error_code krb5_kt_add_ad_entries(krb5_context ctx, char **sprincs_str, + * krb5_kvno kvno, uint_t flags, char *password) + * + * Adds keys to the keytab file for a default set of service principals in an + * Active Directory environment. + * + * where ctx is the pointer passed back from krb5_init_context + * where sprincs_str is an array of service principal names to be added + * to the keytab file, terminated by a NULL pointer + * where domain is the domain used to fully qualify the hostname for + * constructing the salt in the string-to-key function. + * where kvno is the key version number of the set of service principal + * keys to be added + * where flags is the set of conditions that affects the key table entries + * current set of defined flags: + * + * encryption type + * --------------- + * 0x00000001 KRB5_KT_FLAG_AES_SUPPORT (core set + AES-256-128 keys added) + * + * where password is the password that will be used to derive the key for + * the associated service principals in the keytab file + * + * Note: this function is used for adding service principals to the + * local /etc/krb5/krb5.keytab (unless KRB5_KTNAME has been set to something + * different, see krb5envvar(5)) file when the client belongs to an AD domain. + * The keytab file is populated differently for an AD domain as the various + * service principals share the same key material, unlike MIT based + * implementations. + * + * Note: For encryption types; the union of the enc type flag and the + * capabilities of the client is used to determine the enc type set to + * populate the keytab file. + * + * Note: The keys are not created for any AES enctypes UNLESS the + * KRB5_KT_FLAG_AES_SUPPORT flag is set and permitted_enctypes has the AES + * enctypes enabled. + * + * Note: In Active Directory environments the salt is constructed by truncating + * the host name to 15 characters and only use the host svc princ as the salt, + * e.g. host/<str15>.<domain>@<realm>. The realm name is determined by parsing + * sprincs_str. The local host name to construct is determined by calling + * gethostname(3C). If AD environments construct salts differently in the + * future or this function is expanded outside of AD environments one could + * derive the salt by sending an initial authentication exchange. + * + * Note: The kvno was previously determined by performing an LDAP query of the + * computer account's msDS-KeyVersionNumber attribute. If the schema changes + * in the future or this function is expanded outside of AD environments then + * one could derive the principal's kvno by requesting a service ticket. + */ +krb5_error_code +krb5_kt_add_ad_entries(krb5_context ctx, char **sprincs_str, char *domain, + krb5_kvno kvno, uint_t flags, char *password) +{ + krb5_principal princ = NULL, salt = NULL, f_princ = NULL; + krb5_keytab kt = NULL; + krb5_enctype *enctypes = NULL, *tenctype, penctype = 0; + char **tprinc, *ptr, *token, *t_host = NULL, *realm; + char localname[MAXHOSTNAMELEN]; + krb5_error_code code; + krb5_boolean similar; + uint_t t_len; + + assert(ctx != NULL && sprincs_str != NULL && *sprincs_str != NULL); + assert(password != NULL && domain != NULL); + + if ((code = krb5_parse_name(ctx, *sprincs_str, &f_princ)) != 0) + return (code); + if (krb5_princ_realm(ctx, f_princ)->length == 0) { + code = EINVAL; + goto cleanup; + } + realm = krb5_princ_realm(ctx, f_princ)->data; + + if (gethostname(localname, MAXHOSTNAMELEN) != 0) { + code = errno; + goto cleanup; + } + token = localname; + + /* + * Local host name could be fully qualified and/or in upper case, but + * usually and appropriately not. + */ + if ((ptr = strchr(token, '.')) != NULL) + ptr = '\0'; + for (ptr = token; *ptr; ptr++) + *ptr = tolower(*ptr); + /* + * Windows servers currently truncate the host name to 15 characters + * and only use the host svc princ as the salt, e.g. + * host/str15.domain@realm + */ + t_len = snprintf(NULL, 0, "host/%.*s.%s@%s", HOST_TRUNC, token, domain, + realm) + 1; + if ((t_host = malloc(t_len)) == NULL) { + code = ENOMEM; + goto cleanup; + } + (void) snprintf(t_host, t_len, "host/%.*s.%s@%s", HOST_TRUNC, token, + domain, realm); + + if ((code = krb5_parse_name(ctx, t_host, &salt)) != 0) + goto cleanup; + + if ((code = kt_open(ctx, &kt)) != 0) + goto cleanup; + + code = krb5_get_permitted_enctypes(ctx, &enctypes); + if (code != 0 || *enctypes == NULL) + goto cleanup; + + for (tprinc = sprincs_str; *tprinc; tprinc++) { + + if ((code = krb5_parse_name(ctx, *tprinc, &princ)) != 0) + goto cleanup; + + for (tenctype = enctypes; *tenctype; tenctype++) { + if ((!(flags & KRB5_KT_FLAG_AES_SUPPORT) && + (*tenctype == AES128 || *tenctype == AES256)) || + (*tenctype == DES3)) { + continue; + } + + if (penctype) { + code = krb5_c_enctype_compare(ctx, *tenctype, + penctype, &similar); + if (code != 0) + goto cleanup; + else if (similar) + continue; + } + + code = kt_add_entry(ctx, kt, princ, salt, *tenctype, + kvno, password); + if (code != 0) + goto cleanup; + + penctype = *tenctype; + } + + krb5_free_principal(ctx, princ); + princ = NULL; + } + +cleanup: + + if (f_princ != NULL) + krb5_free_principal(ctx, f_princ); + if (salt != NULL) + krb5_free_principal(ctx, salt); + if (t_host != NULL) + free(t_host); + if (kt != NULL) + (void) krb5_kt_close(ctx, kt); + if (enctypes != NULL) + krb5_free_ktypes(ctx, enctypes); + if (princ != NULL) + krb5_free_principal(ctx, princ); + + return (code); +} + +#define PRINCIPAL 0 +#define REALM 1 + +static krb5_error_code +kt_remove_by_key(krb5_context ctx, char *key, uint_t type) +{ + krb5_error_code code; + krb5_kt_cursor cursor; + krb5_keytab_entry entry; + krb5_keytab kt = NULL; + krb5_principal svc_princ = NULL; + krb5_principal_data realm_data; + boolean_t found = FALSE; + + assert(ctx != NULL && key != NULL); + + if (type == REALM) { + krb5_princ_realm(ctx, &realm_data)->length = strlen(key); + krb5_princ_realm(ctx, &realm_data)->data = key; + } else if (type == PRINCIPAL) { + if ((code = krb5_parse_name(ctx, key, &svc_princ)) != 0) + goto cleanup; + } else + return (EINVAL); + + if ((code = kt_open(ctx, &kt)) != 0) + goto cleanup; + + if ((code = krb5_kt_start_seq_get(ctx, kt, &cursor)) != 0) + goto cleanup; + + while ((code = krb5_kt_next_entry(ctx, kt, &entry, &cursor)) == 0) { + if (type == PRINCIPAL && krb5_principal_compare(ctx, svc_princ, + entry.principal)) { + found = TRUE; + } else if (type == REALM && krb5_realm_compare(ctx, &realm_data, + entry.principal)) { + found = TRUE; + } + + if (found == TRUE) { + code = krb5_kt_end_seq_get(ctx, kt, &cursor); + if (code != 0) { + krb5_kt_free_entry(ctx, &entry); + goto cleanup; + } + + code = krb5_kt_remove_entry(ctx, kt, &entry); + if (code != 0) { + krb5_kt_free_entry(ctx, &entry); + goto cleanup; + } + + code = krb5_kt_start_seq_get(ctx, kt, &cursor); + if (code != 0) { + krb5_kt_free_entry(ctx, &entry); + goto cleanup; + } + + found = FALSE; + } + + krb5_kt_free_entry(ctx, &entry); + } + + if (code && code != KRB5_KT_END) + goto cleanup; + + code = krb5_kt_end_seq_get(ctx, kt, &cursor); + +cleanup: + + if (svc_princ != NULL) + krb5_free_principal(ctx, svc_princ); + if (kt != NULL) + (void) krb5_kt_close(ctx, kt); + + return (code); +} + +/* + * krb5_error_code krb5_kt_remove_by_realm(krb5_context ctx, char *realm) + * + * Removes all key entries in the keytab file that match the exact realm name + * specified. + * + * where ctx is the pointer passed back from krb5_init_context + * where realm is the realm name that is matched for any keytab entries + * to be removed + * + * Note: if there are no entries matching realm then 0 (success) is returned + */ +krb5_error_code +krb5_kt_remove_by_realm(krb5_context ctx, char *realm) +{ + + return (kt_remove_by_key(ctx, realm, REALM)); +} + +/* + * krb5_error_code krb5_kt_remove_by_svcprinc(krb5_context ctx, + * char *sprinc_str) + * + * Removes all key entries in the keytab file that match the exact service + * principal name specified. + * + * where ctx is the pointer passed back from krb5_init_context + * where sprinc_str is the service principal name that is matched for any + * keytab entries to be removed + * + * Note: if there are no entries matching sprinc_str then 0 (success) is + * returned + */ +krb5_error_code +krb5_kt_remove_by_svcprinc(krb5_context ctx, char *sprinc_str) +{ + + return (kt_remove_by_key(ctx, sprinc_str, PRINCIPAL)); +} + +/* + * krb5_error_code krb5_kt_validate(krb5_context ctx, char *sprinc_str, + * uint_t flags, boolean_t *valid) + * + * The validate function determines that the service principal exists and that + * it has a valid set of encryption types for said principal. + * + * where ctx is the pointer passed back from krb5_init_context + * where sprinc_str is the principal to be validated in the keytab file + * where flags is the set of conditions that affects the key table entries + * that the function considers valid + * current set of defined flags: + * + * encryption type + * --------------- + * 0x00000001 KRB5_KT_FLAG_AES_SUPPORT (core set + AES-256-128 keys are + * valid) + * + * where valid is a boolean that is set if the sprinc_str is correctly + * populated in the keytab file based on the flags set else valid is unset. + * + * Note: The validate function assumes that only one set of keys exists for + * a corresponding service principal, of key version number (kvno) n. It would + * consider more than one kvno set as invalid. This is from the fact that AD + * clients will attempt to refresh credential caches if KRB5KRB_AP_ERR_MODIFIED + * is returned by the acceptor when the requested kvno is not found within the + * keytab file. + */ +krb5_error_code +krb5_kt_ad_validate(krb5_context ctx, char *sprinc_str, uint_t flags, + boolean_t *valid) +{ + krb5_error_code code; + krb5_kt_cursor cursor; + krb5_keytab_entry entry; + krb5_keytab kt = NULL; + krb5_principal svc_princ = NULL; + krb5_enctype *enctypes, *tenctype, penctype = 0; + boolean_t ck_aes = FALSE; + uint_t aes_count = 0, kt_entries = 0; + krb5_boolean similar; + + assert(ctx != NULL && sprinc_str != NULL && valid != NULL); + + *valid = FALSE; + ck_aes = flags & KRB5_KT_FLAG_AES_SUPPORT; + + if ((code = krb5_parse_name(ctx, sprinc_str, &svc_princ)) != 0) + goto cleanup; + + if ((code = kt_open(ctx, &kt)) != 0) + goto cleanup; + + code = krb5_get_permitted_enctypes(ctx, &enctypes); + if (code != 0 || *enctypes == NULL) + goto cleanup; + + if ((code = krb5_kt_start_seq_get(ctx, kt, &cursor)) != 0) + goto cleanup; + + while ((code = krb5_kt_next_entry(ctx, kt, &entry, &cursor)) == 0) { + if (krb5_principal_compare(ctx, svc_princ, entry.principal)) { + + for (tenctype = enctypes; *tenctype; tenctype++) { + if (penctype) { + code = krb5_c_enctype_compare(ctx, + *tenctype, penctype, &similar); + if (code != 0) { + krb5_kt_free_entry(ctx, &entry); + goto cleanup; + } else if (similar) + continue; + } + + if ((*tenctype != DES3) && + (entry.key.enctype == *tenctype)) { + kt_entries++; + } + + penctype = *tenctype; + } + + if ((entry.key.enctype == AES128) || + (entry.key.enctype == AES256)) { + aes_count++; + } + } + + krb5_kt_free_entry(ctx, &entry); + } + + if (code && code != KRB5_KT_END) + goto cleanup; + + if ((code = krb5_kt_end_seq_get(ctx, kt, &cursor))) + goto cleanup; + + if (ck_aes == TRUE) { + if ((kt_entries != SVC_ENTRIES) || (aes_count != AES_ENTRIES)) + goto cleanup; + } else if (kt_entries != (SVC_ENTRIES - AES_ENTRIES)) + goto cleanup; + + *valid = TRUE; + +cleanup: + + if (svc_princ != NULL) + krb5_free_principal(ctx, svc_princ); + if (kt != NULL) + (void) krb5_kt_close(ctx, kt); + if (enctypes != NULL) + krb5_free_ktypes(ctx, enctypes); + + return (code); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/lib/gss_mechs/mech_krb5/krb5/keytab/kt_solaris.h Wed Aug 11 13:08:50 2010 -0600 @@ -0,0 +1,40 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + */ + +#ifndef _KT_SOLARIS_H +#define _KT_SOLARIS_H + +#define KRB5_KT_FLAG_AES_SUPPORT 1 + +krb5_error_code krb5_kt_add_ad_entries(krb5_context, char **, char *, krb5_kvno, + uint_t, char *); + +krb5_error_code krb5_kt_remove_by_realm(krb5_context, char *); + +krb5_error_code krb5_kt_remove_by_svcprinc(krb5_context, char *); + +krb5_error_code krb5_kt_ad_validate(krb5_context, char *, uint_t, boolean_t *); + +#endif /* _KT_SOLARIS_H */
--- a/usr/src/lib/gss_mechs/mech_krb5/mapfile-vers Wed Aug 11 11:18:58 2010 -0700 +++ b/usr/src/lib/gss_mechs/mech_krb5/mapfile-vers Wed Aug 11 13:08:50 2010 -0600 @@ -500,6 +500,7 @@ krb5_is_referral_realm; krb5_is_thread_safe; krb5_kdc_rep_decrypt_proc; + krb5_kt_add_ad_entries; krb5_kt_add_entry; krb5_kt_close; krb5_kt_default; @@ -514,9 +515,12 @@ krb5_kt_next_entry; krb5_kt_read_service_key; krb5_kt_register; + krb5_kt_remove_by_realm; + krb5_kt_remove_by_svcprinc; krb5_kt_remove_entry; krb5_kt_resolve; krb5_kt_start_seq_get; + krb5_kt_ad_validate; krb5_ktf_ops; krb5_ktf_writable_ops; krb5_ktfile_add;