freeipa/util/ipa_krb5.c
2011-11-17 16:15:24 -05:00

533 lines
14 KiB
C

/*
* Kerberos related utils for FreeIPA
*
* Authors: Simo Sorce <ssorce@redhat.com>
*
* Copyright (C) 2011 Simo Sorce, Red Hat
* see file 'COPYING' for use and warranty information
*
* This program is free software you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <lber.h>
#include <errno.h>
#include "ipa_krb5.h"
/* Salt types */
#define KRB5P_SALT_SIZE 16
static krb5_error_code ipa_get_random_salt(krb5_context krbctx,
krb5_data *salt)
{
krb5_error_code kerr;
int i, v;
/* make random salt */
salt->length = KRB5P_SALT_SIZE;
salt->data = malloc(KRB5P_SALT_SIZE);
if (!salt->data) {
return ENOMEM;
}
kerr = krb5_c_random_make_octets(krbctx, salt);
if (kerr) {
return kerr;
}
/* Windows treats the salt as a string.
* To avoid any compatibility issue, limits octects only to
* the ASCII printable range, or 0x20 <= val <= 0x7E */
for (i = 0; i < salt->length; i++) {
v = (unsigned char)salt->data[i];
v %= 0x5E; /* 7E - 20 */
v += 0x20; /* add base */
salt->data[i] = v;
}
return 0;
}
void
ipa_krb5_free_ktypes(krb5_context context, krb5_enctype *val)
{
free(val);
}
/*
* Convert a krb5_principal into the default salt for that principal.
*/
krb5_error_code ipa_krb5_principal2salt_norealm(krb5_context context,
krb5_const_principal pr,
krb5_data *ret)
{
unsigned int size = 0, offset=0;
krb5_int32 nelem;
register int i;
if (pr == NULL) {
ret->length = 0;
ret->data = NULL;
return 0;
}
nelem = krb5_princ_size(context, pr);
for (i = 0; i < (int) nelem; i++)
size += krb5_princ_component(context, pr, i)->length;
ret->length = size;
if (!(ret->data = malloc (size)))
return ENOMEM;
for (i = 0; i < (int) nelem; i++) {
memcpy(&ret->data[offset], krb5_princ_component(context, pr, i)->data,
krb5_princ_component(context, pr, i)->length);
offset += krb5_princ_component(context, pr, i)->length;
}
return 0;
}
void krb5int_c_free_keyblock_contents(krb5_context context,
register krb5_keyblock *key);
/*
* Generate a krb5_key_data set by encrypting keys according to
* enctype/salttype preferences
*/
krb5_error_code ipa_krb5_generate_key_data(krb5_context krbctx,
krb5_principal principal,
krb5_data pwd, int kvno,
krb5_keyblock *kmkey,
int num_encsalts,
krb5_key_salt_tuple *encsalts,
int *_num_keys,
krb5_key_data **_keys)
{
krb5_error_code kerr;
krb5_key_data *keys;
int num_keys;
int i;
num_keys = num_encsalts;
keys = calloc(num_keys, sizeof(krb5_key_data));
if (!keys) {
return ENOMEM;
}
for (i = 0; i < num_keys; i++) {
krb5_keyblock key;
krb5_data salt;
krb5_octet *ptr;
krb5_data plain;
krb5_enc_data cipher;
krb5_int16 t;
size_t len;
salt.data = NULL;
keys[i].key_data_ver = 2; /* we always have a salt */
keys[i].key_data_kvno = kvno;
switch (encsalts[i].ks_salttype) {
case KRB5_KDB_SALTTYPE_ONLYREALM:
if (!principal->realm.data) {
kerr = EINVAL;
goto done;
}
salt.length = principal->realm.length;
salt.data = malloc(salt.length);
if (!salt.data) {
kerr = ENOMEM;
goto done;
}
memcpy(salt.data, principal->realm.data, salt.length);
break;
case KRB5_KDB_SALTTYPE_NOREALM:
kerr = ipa_krb5_principal2salt_norealm(krbctx, principal, &salt);
if (kerr) {
goto done;
}
break;
case KRB5_KDB_SALTTYPE_NORMAL:
kerr = krb5_principal2salt(krbctx, principal, &salt);
if (kerr) {
goto done;
}
break;
case KRB5_KDB_SALTTYPE_SPECIAL:
kerr = ipa_get_random_salt(krbctx, &salt);
if (kerr) {
goto done;
}
break;
case KRB5_KDB_SALTTYPE_V4:
salt.length = 0;
break;
case KRB5_KDB_SALTTYPE_AFS3:
if (!principal->realm.data) {
kerr = EINVAL;
goto done;
}
salt.data = strndup((char *)principal->realm.data,
principal->realm.length);
if (!salt.data) {
kerr = ENOMEM;
goto done;
}
salt.length = SALT_TYPE_AFS_LENGTH; /* special value */
break;
default:
kerr = EINVAL;
goto done;
}
/* need to build the key now to manage the AFS salt.length
* special case */
kerr = krb5_c_string_to_key(krbctx,
encsalts[i].ks_enctype,
&pwd, &salt, &key);
if (kerr) {
krb5_free_data_contents(krbctx, &salt);
goto done;
}
if (salt.length == SALT_TYPE_AFS_LENGTH) {
salt.length = strlen(salt.data);
}
kerr = krb5_c_encrypt_length(krbctx,
kmkey->enctype, key.length, &len);
if (kerr) {
krb5int_c_free_keyblock_contents(krbctx, &key);
krb5_free_data_contents(krbctx, &salt);
goto done;
}
if ((ptr = (krb5_octet *) malloc(2 + len)) == NULL) {
kerr = ENOMEM;
krb5int_c_free_keyblock_contents(krbctx, &key);
krb5_free_data_contents(krbctx, &salt);
goto done;
}
t = htole16(key.length);
memcpy(ptr, &t, 2);
plain.length = key.length;
plain.data = (char *)key.contents;
cipher.ciphertext.length = len;
cipher.ciphertext.data = (char *)ptr+2;
kerr = krb5_c_encrypt(krbctx, kmkey, 0, 0, &plain, &cipher);
if (kerr) {
krb5int_c_free_keyblock_contents(krbctx, &key);
krb5_free_data_contents(krbctx, &salt);
free(ptr);
goto done;
}
/* KrbSalt */
keys[i].key_data_type[1] = encsalts[i].ks_salttype;
if (salt.length) {
keys[i].key_data_length[1] = salt.length;
keys[i].key_data_contents[1] = (krb5_octet *)salt.data;
}
/* EncryptionKey */
keys[i].key_data_type[0] = key.enctype;
keys[i].key_data_length[0] = len + 2;
keys[i].key_data_contents[0] = malloc(len + 2);
if (!keys[i].key_data_contents[0]) {
kerr = ENOMEM;
krb5int_c_free_keyblock_contents(krbctx, &key);
free(ptr);
goto done;
}
memcpy(keys[i].key_data_contents[0], ptr, len + 2);
/* make sure we free the memory used now that we are done with it */
krb5int_c_free_keyblock_contents(krbctx, &key);
free(ptr);
}
*_num_keys = num_keys;
*_keys = keys;
kerr = 0;
done:
if (kerr) {
ipa_krb5_free_key_data(keys, num_keys);
}
return kerr;
}
void ipa_krb5_free_key_data(krb5_key_data *keys, int num_keys)
{
int i;
for (i = 0; i < num_keys; i++) {
/* try to wipe key from memory,
* hopefully the compiler will not optimize it away */
if (keys[i].key_data_length[0]) {
memset(keys[i].key_data_contents[0],
0, keys[i].key_data_length[0]);
}
free(keys[i].key_data_contents[0]);
free(keys[i].key_data_contents[1]);
}
free(keys);
}
/* Novell key-format scheme:
KrbKeySet ::= SEQUENCE {
attribute-major-vno [0] UInt16,
attribute-minor-vno [1] UInt16,
kvno [2] UInt32,
mkvno [3] UInt32 OPTIONAL,
keys [4] SEQUENCE OF KrbKey,
...
}
KrbKey ::= SEQUENCE {
salt [0] KrbSalt OPTIONAL,
key [1] EncryptionKey,
s2kparams [2] OCTET STRING OPTIONAL,
...
}
KrbSalt ::= SEQUENCE {
type [0] Int32,
salt [1] OCTET STRING OPTIONAL
}
EncryptionKey ::= SEQUENCE {
keytype [0] Int32,
keyvalue [1] OCTET STRING
}
*/
int ber_encode_krb5_key_data(krb5_key_data *data,
int numk, int mkvno,
struct berval **encoded)
{
BerElement *be = NULL;
ber_tag_t tag;
int ret, i;
be = ber_alloc_t(LBER_USE_DER);
if (!be) {
return ENOMEM;
}
tag = LBER_CONSTRUCTED | LBER_CLASS_CONTEXT;
ret = ber_printf(be, "{t[i]t[i]t[i]t[i]t[{",
tag | 0, 1, tag | 1, 1,
tag | 2, (ber_int_t)data[0].key_data_kvno,
tag | 3, (ber_int_t)mkvno, tag | 4);
if (ret == -1) {
ret = EFAULT;
goto done;
}
for (i = 0; i < numk; i++) {
ret = ber_printf(be, "{");
if (ret == -1) {
ret = EFAULT;
goto done;
}
if (data[i].key_data_length[1] != 0) {
ret = ber_printf(be, "t[{t[i]",
tag | 0,
tag | 0,
(ber_int_t)data[i].key_data_type[1]);
if (ret != -1) {
ret = ber_printf(be, "t[o]",
tag | 1,
data[i].key_data_contents[1],
(ber_len_t)data[i].key_data_length[1]);
}
if (ret != -1) {
ret = ber_printf(be, "}]");
}
if (ret == -1) {
ret = EFAULT;
goto done;
}
}
ret = ber_printf(be, "t[{t[i]t[o]}]",
tag | 1,
tag | 0,
(ber_int_t)data[i].key_data_type[0],
tag | 1,
data[i].key_data_contents[0],
(ber_len_t)data[i].key_data_length[0]);
if (ret == -1) {
ret = EFAULT;
goto done;
}
ret = ber_printf(be, "}");
if (ret == -1) {
ret = EFAULT;
goto done;
}
}
ret = ber_printf(be, "}]}");
if (ret == -1) {
ret = EFAULT;
goto done;
}
ret = ber_flatten(be, encoded);
if (ret == -1) {
ret = EFAULT;
goto done;
}
done:
ber_free(be, 1);
return ret;
}
krb5_error_code parse_bval_key_salt_tuples(krb5_context kcontext,
const char * const *vals,
int n_vals,
krb5_key_salt_tuple **kst,
int *n_kst)
{
krb5_error_code kerr;
krb5_key_salt_tuple *ks;
int n_ks;
int i;
ks = calloc(n_vals + 1, sizeof(krb5_key_salt_tuple));
if (!ks) {
return ENOMEM;
}
for (i = 0, n_ks = 0; i < n_vals; i++) {
char *enc, *salt;
krb5_int32 tmpsalt;
krb5_enctype tmpenc;
krb5_boolean similar;
krb5_error_code krberr;
int j;
enc = strdup(vals[i]);
if (!enc) {
kerr = ENOMEM;
goto fail;
}
salt = strchr(enc, ':');
if (!salt) {
free(enc);
continue;
}
*salt = '\0'; /* null terminate the enc type */
salt++; /* skip : */
krberr = krb5_string_to_enctype(enc, &tmpenc);
if (krberr) {
free(enc);
continue;
}
krberr = krb5_string_to_salttype(salt, &tmpsalt);
for (j = 0; j < n_ks; j++) {
krb5_c_enctype_compare(kcontext,
ks[j].ks_enctype, tmpenc, &similar);
if (similar && (ks[j].ks_salttype == tmpsalt)) {
break;
}
}
if (j == n_ks) {
/* not found */
ks[j].ks_enctype = tmpenc;
ks[j].ks_salttype = tmpsalt;
n_ks++;
}
free(enc);
}
*kst = ks;
*n_kst = n_ks;
return 0;
fail:
free(ks);
return kerr;
}
krb5_error_code filter_key_salt_tuples(krb5_context context,
krb5_key_salt_tuple *req, int n_req,
krb5_key_salt_tuple *supp, int n_supp,
krb5_key_salt_tuple **res, int *n_res)
{
krb5_key_salt_tuple *ks = NULL;
int n_ks;
int i, j;
ks = calloc(n_req, sizeof(krb5_key_salt_tuple));
if (!ks) {
return ENOMEM;
}
n_ks = 0;
for (i = 0; i < n_req; i++) {
for (j = 0; j < n_supp; j++) {
if (req[i].ks_enctype == supp[j].ks_enctype &&
req[i].ks_salttype == supp[j].ks_salttype) {
break;
}
}
if (j < n_supp) {
ks[n_ks] = req[i];
n_ks++;
}
}
*res = ks;
*n_res = n_ks;
return 0;
}