2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A
2N/A/*
2N/A * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A
2N/A#include "k5-int.h"
2N/A#include <errno.h>
2N/A#include <netdb.h>
2N/A#include <strings.h>
2N/A#include <stdio.h>
2N/A#include <assert.h>
2N/A#include <ctype.h>
2N/A#include "kt_solaris.h"
2N/A
2N/A#define AES128 ENCTYPE_AES128_CTS_HMAC_SHA1_96
2N/A#define AES256 ENCTYPE_AES256_CTS_HMAC_SHA1_96
2N/A#define DES3 ENCTYPE_DES3_CBC_SHA1
2N/A#define AES_ENTRIES 2
2N/A#define HOST_TRUNC 15
2N/A#define SVC_ENTRIES 4
2N/A
2N/Astatic krb5_error_code
2N/Ak5_kt_open(krb5_context ctx, krb5_keytab *kt)
2N/A{
2N/A krb5_error_code code;
2N/A char buf[MAX_KEYTAB_NAME_LEN], ktstr[MAX_KEYTAB_NAME_LEN];
2N/A
2N/A memset(buf, 0, sizeof (buf));
2N/A memset(ktstr, 0, sizeof (ktstr));
2N/A
2N/A if ((code = krb5_kt_default_name(ctx, buf, sizeof (buf))) != 0)
2N/A return (code);
2N/A
2N/A /*
2N/A * The default is file type w/o the write. If it's anything besides
2N/A * FILE or WRFILE then we bail as quickly as possible.
2N/A */
2N/A if (strncmp(buf, "FILE:", strlen("FILE:")) == 0)
2N/A (void) snprintf(ktstr, sizeof (ktstr), "WR%s", buf);
2N/A else if (strncmp(buf, "WRFILE:", strlen("WRFILE:")) == 0)
2N/A (void) snprintf(ktstr, sizeof (ktstr), "%s", buf);
2N/A else
2N/A return (EINVAL);
2N/A
2N/A return (krb5_kt_resolve(ctx, ktstr, kt));
2N/A}
2N/A
2N/Astatic krb5_error_code
2N/Ak5_kt_add_entry(krb5_context ctx, krb5_keytab kt, const krb5_principal princ,
2N/A const krb5_principal svc_princ, krb5_enctype enctype, krb5_kvno kvno,
2N/A const char *pw)
2N/A{
2N/A krb5_keytab_entry entry;
2N/A krb5_data password, salt;
2N/A krb5_keyblock key;
2N/A krb5_error_code code;
2N/A
2N/A memset(&entry, 0, sizeof (entry));
2N/A memset(&key, 0, sizeof (krb5_keyblock));
2N/A
2N/A password.length = strlen(pw);
2N/A password.data = (char *)pw;
2N/A
2N/A if ((code = krb5_principal2salt(ctx, svc_princ, &salt)) != 0) {
2N/A return (code);
2N/A }
2N/A
2N/A if ((krb5_c_string_to_key(ctx, enctype, &password, &salt, &key)) != 0)
2N/A goto cleanup;
2N/A
2N/A entry.key = key;
2N/A entry.vno = kvno;
2N/A entry.principal = princ;
2N/A
2N/A code = krb5_kt_add_entry(ctx, kt, &entry);
2N/A
2N/Acleanup:
2N/A
2N/A krb5_xfree(salt.data);
2N/A krb5_free_keyblock_contents(ctx, &key);
2N/A
2N/A return (code);
2N/A}
2N/A
2N/A/*
2N/A * krb5_error_code k5_kt_add_ad_entries(krb5_context ctx, char **sprincs_str,
2N/A * krb5_kvno kvno, uint_t flags, char *password)
2N/A *
2N/A * Adds keys to the keytab file for a default set of service principals in an
2N/A * Active Directory environment.
2N/A *
2N/A * where ctx is the pointer passed back from krb5_init_context
2N/A * where sprincs_str is an array of service principal names to be added
2N/A * to the keytab file, terminated by a NULL pointer
2N/A * where domain is the domain used to fully qualify the hostname for
2N/A * constructing the salt in the string-to-key function.
2N/A * where kvno is the key version number of the set of service principal
2N/A * keys to be added
2N/A * where flags is the set of conditions that affects the key table entries
2N/A * current set of defined flags:
2N/A *
2N/A * encryption type
2N/A * ---------------
2N/A * 0x00000001 K5_KT_FLAG_AES_SUPPORT (core set + AES-256-128 keys added)
2N/A *
2N/A * where password is the password that will be used to derive the key for
2N/A * the associated service principals in the keytab file
2N/A *
2N/A * Note: this function is used for adding service principals to the
2N/A * local /etc/krb5/krb5.keytab (unless KRB5_KTNAME has been set to something
2N/A * different, see krb5envvar(5)) file when the client belongs to an AD domain.
2N/A * The keytab file is populated differently for an AD domain as the various
2N/A * service principals share the same key material, unlike MIT based
2N/A * implementations.
2N/A *
2N/A * Note: For encryption types; the union of the enc type flag and the
2N/A * capabilities of the client is used to determine the enc type set to
2N/A * populate the keytab file.
2N/A *
2N/A * Note: The keys are not created for any AES enctypes UNLESS the
2N/A * K5_KT_FLAG_AES_SUPPORT flag is set and permitted_enctypes has the AES
2N/A * enctypes enabled.
2N/A *
2N/A * Note: In Active Directory environments the salt is constructed by truncating
2N/A * the host name to 15 characters and only use the host svc princ as the salt,
2N/A * e.g. host/<str15>.<domain>@<realm>. The realm name is determined by parsing
2N/A * sprincs_str. The local host name to construct is determined by calling
2N/A * gethostname(3C). If AD environments construct salts differently in the
2N/A * future or this function is expanded outside of AD environments one could
2N/A * derive the salt by sending an initial authentication exchange.
2N/A *
2N/A * Note: The kvno was previously determined by performing an LDAP query of the
2N/A * computer account's msDS-KeyVersionNumber attribute. If the schema changes
2N/A * in the future or this function is expanded outside of AD environments then
2N/A * one could derive the principal's kvno by requesting a service ticket.
2N/A */
2N/Akrb5_error_code
2N/Ak5_kt_add_ad_entries(krb5_context ctx, char **sprincs_str, char *domain,
2N/A krb5_kvno kvno, uint_t flags, char *password)
2N/A{
2N/A krb5_principal princ = NULL, salt = NULL, f_princ = NULL;
2N/A krb5_keytab kt = NULL;
2N/A krb5_enctype *enctypes = NULL, *tenctype, penctype = 0;
2N/A char **tprinc, *ptr, *token, *t_host = NULL, *realm;
2N/A char localname[MAXHOSTNAMELEN];
2N/A krb5_error_code code;
2N/A krb5_boolean similar;
2N/A uint_t t_len;
2N/A
2N/A assert(ctx != NULL && sprincs_str != NULL && *sprincs_str != NULL);
2N/A assert(password != NULL && domain != NULL);
2N/A
2N/A if ((code = krb5_parse_name(ctx, *sprincs_str, &f_princ)) != 0)
2N/A return (code);
2N/A if (krb5_princ_realm(ctx, f_princ)->length == 0) {
2N/A code = EINVAL;
2N/A goto cleanup;
2N/A }
2N/A realm = krb5_princ_realm(ctx, f_princ)->data;
2N/A
2N/A if (gethostname(localname, MAXHOSTNAMELEN) != 0) {
2N/A code = errno;
2N/A goto cleanup;
2N/A }
2N/A token = localname;
2N/A
2N/A /*
2N/A * Local host name could be fully qualified and/or in upper case, but
2N/A * usually and appropriately not.
2N/A */
2N/A if ((ptr = strchr(token, '.')) != NULL)
2N/A ptr = '\0';
2N/A for (ptr = token; *ptr; ptr++)
2N/A *ptr = tolower(*ptr);
2N/A /*
2N/A * Windows servers currently truncate the host name to 15 characters
2N/A * and only use the host svc princ as the salt, e.g.
2N/A * host/str15.domain@realm
2N/A */
2N/A t_len = snprintf(NULL, 0, "host/%.*s.%s@%s", HOST_TRUNC, token, domain,
2N/A realm) + 1;
2N/A if ((t_host = malloc(t_len)) == NULL) {
2N/A code = ENOMEM;
2N/A goto cleanup;
2N/A }
2N/A (void) snprintf(t_host, t_len, "host/%.*s.%s@%s", HOST_TRUNC, token,
2N/A domain, realm);
2N/A
2N/A if ((code = krb5_parse_name(ctx, t_host, &salt)) != 0)
2N/A goto cleanup;
2N/A
2N/A if ((code = k5_kt_open(ctx, &kt)) != 0)
2N/A goto cleanup;
2N/A
2N/A code = krb5_get_permitted_enctypes(ctx, &enctypes);
2N/A if (code != 0 || *enctypes == NULL)
2N/A goto cleanup;
2N/A
2N/A for (tprinc = sprincs_str; *tprinc; tprinc++) {
2N/A
2N/A if ((code = krb5_parse_name(ctx, *tprinc, &princ)) != 0)
2N/A goto cleanup;
2N/A
2N/A for (tenctype = enctypes; *tenctype; tenctype++) {
2N/A if ((!(flags & K5_KT_FLAG_AES_SUPPORT) &&
2N/A (*tenctype == AES128 || *tenctype == AES256)) ||
2N/A (*tenctype == DES3)) {
2N/A continue;
2N/A }
2N/A
2N/A if (penctype) {
2N/A code = krb5_c_enctype_compare(ctx, *tenctype,
2N/A penctype, &similar);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A else if (similar)
2N/A continue;
2N/A }
2N/A
2N/A code = k5_kt_add_entry(ctx, kt, princ, salt, *tenctype,
2N/A kvno, password);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A
2N/A penctype = *tenctype;
2N/A }
2N/A
2N/A krb5_free_principal(ctx, princ);
2N/A princ = NULL;
2N/A penctype = NULL;
2N/A }
2N/A
2N/Acleanup:
2N/A
2N/A if (f_princ != NULL)
2N/A krb5_free_principal(ctx, f_princ);
2N/A if (salt != NULL)
2N/A krb5_free_principal(ctx, salt);
2N/A if (t_host != NULL)
2N/A free(t_host);
2N/A if (kt != NULL)
2N/A (void) krb5_kt_close(ctx, kt);
2N/A if (enctypes != NULL)
2N/A krb5_free_ktypes(ctx, enctypes);
2N/A if (princ != NULL)
2N/A krb5_free_principal(ctx, princ);
2N/A
2N/A return (code);
2N/A}
2N/A
2N/A#define PRINCIPAL 0
2N/A#define REALM 1
2N/A
2N/Astatic krb5_error_code
2N/Ak5_kt_remove_by_key(krb5_context ctx, char *key, uint_t type)
2N/A{
2N/A krb5_error_code code;
2N/A krb5_kt_cursor cursor;
2N/A krb5_keytab_entry entry;
2N/A krb5_keytab kt = NULL;
2N/A krb5_principal svc_princ = NULL;
2N/A krb5_principal_data realm_data;
2N/A boolean_t found = FALSE;
2N/A
2N/A assert(ctx != NULL && key != NULL);
2N/A
2N/A if (type == REALM) {
2N/A krb5_princ_realm(ctx, &realm_data)->length = strlen(key);
2N/A krb5_princ_realm(ctx, &realm_data)->data = key;
2N/A } else if (type == PRINCIPAL) {
2N/A if ((code = krb5_parse_name(ctx, key, &svc_princ)) != 0)
2N/A goto cleanup;
2N/A } else
2N/A return (EINVAL);
2N/A
2N/A if ((code = k5_kt_open(ctx, &kt)) != 0)
2N/A goto cleanup;
2N/A
2N/A if ((code = krb5_kt_start_seq_get(ctx, kt, &cursor)) != 0)
2N/A goto cleanup;
2N/A
2N/A while ((code = krb5_kt_next_entry(ctx, kt, &entry, &cursor)) == 0) {
2N/A if (type == PRINCIPAL && krb5_principal_compare(ctx, svc_princ,
2N/A entry.principal)) {
2N/A found = TRUE;
2N/A } else if (type == REALM && krb5_realm_compare(ctx, &realm_data,
2N/A entry.principal)) {
2N/A found = TRUE;
2N/A }
2N/A
2N/A if (found == TRUE) {
2N/A code = krb5_kt_end_seq_get(ctx, kt, &cursor);
2N/A if (code != 0) {
2N/A krb5_kt_free_entry(ctx, &entry);
2N/A goto cleanup;
2N/A }
2N/A
2N/A code = krb5_kt_remove_entry(ctx, kt, &entry);
2N/A if (code != 0) {
2N/A krb5_kt_free_entry(ctx, &entry);
2N/A goto cleanup;
2N/A }
2N/A
2N/A code = krb5_kt_start_seq_get(ctx, kt, &cursor);
2N/A if (code != 0) {
2N/A krb5_kt_free_entry(ctx, &entry);
2N/A goto cleanup;
2N/A }
2N/A
2N/A found = FALSE;
2N/A }
2N/A
2N/A krb5_kt_free_entry(ctx, &entry);
2N/A }
2N/A
2N/A if (code && code != KRB5_KT_END)
2N/A goto cleanup;
2N/A
2N/A code = krb5_kt_end_seq_get(ctx, kt, &cursor);
2N/A
2N/Acleanup:
2N/A
2N/A if (svc_princ != NULL)
2N/A krb5_free_principal(ctx, svc_princ);
2N/A if (kt != NULL)
2N/A (void) krb5_kt_close(ctx, kt);
2N/A
2N/A return (code);
2N/A}
2N/A
2N/A/*
2N/A * krb5_error_code k5_kt_remove_by_realm(krb5_context ctx, char *realm)
2N/A *
2N/A * Removes all key entries in the keytab file that match the exact realm name
2N/A * specified.
2N/A *
2N/A * where ctx is the pointer passed back from krb5_init_context
2N/A * where realm is the realm name that is matched for any keytab entries
2N/A * to be removed
2N/A *
2N/A * Note: if there are no entries matching realm then 0 (success) is returned
2N/A */
2N/Akrb5_error_code
2N/Ak5_kt_remove_by_realm(krb5_context ctx, char *realm)
2N/A{
2N/A
2N/A return (k5_kt_remove_by_key(ctx, realm, REALM));
2N/A}
2N/A
2N/A/*
2N/A * krb5_error_code k5_kt_remove_by_svcprinc(krb5_context ctx, char *sprinc_str)
2N/A *
2N/A * Removes all key entries in the keytab file that match the exact service
2N/A * principal name specified.
2N/A *
2N/A * where ctx is the pointer passed back from krb5_init_context
2N/A * where sprinc_str is the service principal name that is matched for any
2N/A * keytab entries to be removed
2N/A *
2N/A * Note: if there are no entries matching sprinc_str then 0 (success) is
2N/A * returned
2N/A */
2N/Akrb5_error_code
2N/Ak5_kt_remove_by_svcprinc(krb5_context ctx, char *sprinc_str)
2N/A{
2N/A
2N/A return (k5_kt_remove_by_key(ctx, sprinc_str, PRINCIPAL));
2N/A}
2N/A
2N/A/*
2N/A * krb5_error_code k5_kt_validate(krb5_context ctx, char *sprinc_str,
2N/A * uint_t flags, boolean_t *valid)
2N/A *
2N/A * The validate function determines that the service principal exists and that
2N/A * it has a valid set of encryption types for said principal.
2N/A *
2N/A * where ctx is the pointer passed back from krb5_init_context
2N/A * where sprinc_str is the principal to be validated in the keytab file
2N/A * where flags is the set of conditions that affects the key table entries
2N/A * that the function considers valid
2N/A * current set of defined flags:
2N/A *
2N/A * encryption type
2N/A * ---------------
2N/A * 0x00000001 K5_KT_FLAG_AES_SUPPORT (core set + AES-256-128 keys are
2N/A * valid)
2N/A *
2N/A * where valid is a boolean that is set if the sprinc_str is correctly
2N/A * populated in the keytab file based on the flags set else valid is unset.
2N/A *
2N/A * Note: The validate function assumes that only one set of keys exists for
2N/A * a corresponding service principal, of key version number (kvno) n. It would
2N/A * consider more than one kvno set as invalid. This is from the fact that AD
2N/A * clients will attempt to refresh credential caches if KRB5KRB_AP_ERR_MODIFIED
2N/A * is returned by the acceptor when the requested kvno is not found within the
2N/A * keytab file.
2N/A */
2N/Akrb5_error_code
2N/Ak5_kt_ad_validate(krb5_context ctx, char *sprinc_str, uint_t flags,
2N/A boolean_t *valid)
2N/A{
2N/A krb5_error_code code;
2N/A krb5_kt_cursor cursor;
2N/A krb5_keytab_entry entry;
2N/A krb5_keytab kt = NULL;
2N/A krb5_principal svc_princ = NULL;
2N/A krb5_enctype *enctypes, *tenctype, penctype = 0;
2N/A boolean_t ck_aes = FALSE;
2N/A uint_t aes_count = 0, kt_entries = 0;
2N/A krb5_boolean similar;
2N/A
2N/A assert(ctx != NULL && sprinc_str != NULL && valid != NULL);
2N/A
2N/A *valid = FALSE;
2N/A ck_aes = flags & K5_KT_FLAG_AES_SUPPORT;
2N/A
2N/A if ((code = krb5_parse_name(ctx, sprinc_str, &svc_princ)) != 0)
2N/A goto cleanup;
2N/A
2N/A if ((code = k5_kt_open(ctx, &kt)) != 0)
2N/A goto cleanup;
2N/A
2N/A code = krb5_get_permitted_enctypes(ctx, &enctypes);
2N/A if (code != 0 || *enctypes == NULL)
2N/A goto cleanup;
2N/A
2N/A if ((code = krb5_kt_start_seq_get(ctx, kt, &cursor)) != 0)
2N/A goto cleanup;
2N/A
2N/A while ((code = krb5_kt_next_entry(ctx, kt, &entry, &cursor)) == 0) {
2N/A if (krb5_principal_compare(ctx, svc_princ, entry.principal)) {
2N/A
2N/A for (tenctype = enctypes; *tenctype; tenctype++) {
2N/A if (penctype) {
2N/A code = krb5_c_enctype_compare(ctx,
2N/A *tenctype, penctype, &similar);
2N/A if (code != 0) {
2N/A krb5_kt_free_entry(ctx, &entry);
2N/A goto cleanup;
2N/A } else if (similar)
2N/A continue;
2N/A }
2N/A
2N/A if ((*tenctype != DES3) &&
2N/A (entry.key.enctype == *tenctype)) {
2N/A kt_entries++;
2N/A }
2N/A
2N/A penctype = *tenctype;
2N/A }
2N/A
2N/A if ((entry.key.enctype == AES128) ||
2N/A (entry.key.enctype == AES256)) {
2N/A aes_count++;
2N/A }
2N/A }
2N/A
2N/A krb5_kt_free_entry(ctx, &entry);
2N/A }
2N/A
2N/A if (code && code != KRB5_KT_END)
2N/A goto cleanup;
2N/A
2N/A if ((code = krb5_kt_end_seq_get(ctx, kt, &cursor)))
2N/A goto cleanup;
2N/A
2N/A if (ck_aes == TRUE) {
2N/A if ((kt_entries != SVC_ENTRIES) || (aes_count != AES_ENTRIES))
2N/A goto cleanup;
2N/A } else if (kt_entries != (SVC_ENTRIES - AES_ENTRIES))
2N/A goto cleanup;
2N/A
2N/A *valid = TRUE;
2N/A
2N/Acleanup:
2N/A
2N/A if (svc_princ != NULL)
2N/A krb5_free_principal(ctx, svc_princ);
2N/A if (kt != NULL)
2N/A (void) krb5_kt_close(ctx, kt);
2N/A if (enctypes != NULL)
2N/A krb5_free_ktypes(ctx, enctypes);
2N/A
2N/A return (code);
2N/A}