mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-28 18:01:23 -06:00
533 lines
14 KiB
C
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;
|
|
}
|
|
|