/* * Kerberos related utils for FreeIPA * * Authors: Simo Sorce * * 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 . */ #include #include #include #include #include #include #define _(STRING) gettext(STRING) #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; } int ber_decode_krb5_key_data(struct berval *encoded, int *m_kvno, int *numk, krb5_key_data **data) { krb5_key_data *keys = NULL; BerElement *be = NULL; void *tmp; int i = 0; ber_tag_t tag; ber_int_t major_vno; ber_int_t minor_vno; ber_int_t kvno; ber_int_t mkvno; ber_int_t type; ber_tag_t seqtag; ber_len_t seqlen; ber_len_t setlen; ber_tag_t retag; ber_tag_t opttag; struct berval tval; int ret; be = ber_alloc_t(LBER_USE_DER); if (!be) { return ENOMEM; } /* reinit the ber element with the new val */ ber_init2(be, encoded, LBER_USE_DER); /* fill key_data struct with the data */ retag = ber_scanf(be, "{t[i]t[i]t[i]t[i]t[{", &tag, &major_vno, &tag, &minor_vno, &tag, &kvno, &tag, &mkvno, &seqtag); if (retag == LBER_ERROR || major_vno != 1 || minor_vno != 1 || seqtag != (LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 4)) { ret = EINVAL; goto done; } retag = ber_skip_tag(be, &seqlen); /* sequence of keys */ for (i = 0; retag == LBER_SEQUENCE; i++) { tmp = realloc(keys, (i + 1) * sizeof(krb5_key_data)); if (!tmp) { ret = ENOMEM; goto done; } keys = tmp; memset(&keys[i], 0, sizeof(krb5_key_data)); keys[i].key_data_kvno = kvno; /* do we have a salt type ? (optional) */ retag = ber_scanf(be, "t", &opttag); if (retag == LBER_ERROR) { ret = EINVAL; goto done; } if (opttag == (LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 0)) { keys[i].key_data_ver = 2; retag = ber_scanf(be, "[l{tl[i]", &seqlen, &tag, &setlen, &type); if (tag != (LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 0)) { ret = EINVAL; goto done; } keys[i].key_data_type[1] = type; /* do we have salt data ? (optional) */ if (seqlen > setlen + 2) { retag = ber_scanf(be, "t[o]", &tag, &tval); if (retag == LBER_ERROR || tag != (LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1)) { ret = EINVAL; goto done; } keys[i].key_data_length[1] = tval.bv_len; keys[i].key_data_contents[1] = (krb5_octet *)tval.bv_val; } retag = ber_scanf(be, "}]t", &opttag); if (retag == LBER_ERROR) { ret = EINVAL; goto done; } } else { keys[i].key_data_ver = 1; } if (opttag != (LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1)) { ret = EINVAL; goto done; } /* get the key */ retag = ber_scanf(be, "[{t[i]t[o]}]", &tag, &type, &tag, &tval); if (retag == LBER_ERROR) { ret = EINVAL; goto done; } keys[i].key_data_type[0] = type; keys[i].key_data_length[0] = tval.bv_len; keys[i].key_data_contents[0] = (krb5_octet *)tval.bv_val; /* check for sk2params */ retag = ber_peek_tag(be, &setlen); if (retag == (LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 2)) { /* not supported yet, skip */ retag = ber_scanf(be, "t[x]}"); } else { retag = ber_scanf(be, "}"); } if (retag == LBER_ERROR) { ret = EINVAL; goto done; } retag = ber_skip_tag(be, &seqlen); } ret = 0; done: ber_free(be, 0); /* internal buffer is 'encoded' */ if (ret) { for (i -= 1; keys && i >= 0; i--) { free(keys[i].key_data_contents[0]); free(keys[i].key_data_contents[1]); } free(keys); keys = NULL; mkvno = 0; } *m_kvno = mkvno; *numk = i; *data = keys; 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; } struct berval *create_key_control(struct keys_container *keys, const char *principalName) { struct krb_key_salt *ksdata; struct berval *bval; BerElement *be; int ret, i; be = ber_alloc_t(LBER_USE_DER); if (!be) { return NULL; } ret = ber_printf(be, "{s{", principalName); if (ret == -1) { ber_free(be, 1); return NULL; } ksdata = keys->ksdata; for (i = 0; i < keys->nkeys; i++) { /* we set only the EncryptionKey and salt, no s2kparams */ ret = ber_printf(be, "{t[{t[i]t[o]}]", (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 0), (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 0), (ber_int_t)ksdata[i].enctype, (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1), (char *)ksdata[i].key.contents, (ber_len_t)ksdata[i].key.length); if (ret == -1) { ber_free(be, 1); return NULL; } if (ksdata[i].salttype == NO_SALT) { ret = ber_printf(be, "}"); continue; } /* we have to pass a salt structure */ ret = ber_printf(be, "t[{t[i]t[o]}]}", (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1), (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 0), (ber_int_t)ksdata[i].salttype, (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1), (char *)ksdata[i].salt.data, (ber_len_t)ksdata[i].salt.length); if (ret == -1) { ber_free(be, 1); return NULL; } } ret = ber_printf(be, "}}"); if (ret == -1) { ber_free(be, 1); return NULL; } ret = ber_flatten(be, &bval); if (ret == -1) { ber_free(be, 1); return NULL; } ber_free(be, 1); return bval; } void free_keys_contents(krb5_context krbctx, struct keys_container *keys) { struct krb_key_salt *ksdata; int i; ksdata = keys->ksdata; for (i = 0; i < keys->nkeys; i++) { krb5_free_keyblock_contents(krbctx, &ksdata[i].key); krb5_free_data_contents(krbctx, &ksdata[i].salt); } free(ksdata); keys->ksdata = NULL; keys->nkeys = 0; } /* Determines Encryption and Salt types, * allocates key_salt data storage, * filters out equivalent encodings, * returns 0 if no enctypes available, >0 if enctypes are available */ static int prep_ksdata(krb5_context krbctx, const char *str, struct keys_container *keys, char **err_msg) { struct krb_key_salt *ksdata; krb5_error_code krberr; int n, i, j, nkeys; *err_msg = NULL; if (str == NULL) { krb5_enctype *ktypes; krberr = krb5_get_permitted_enctypes(krbctx, &ktypes); if (krberr) { *err_msg = _("No system preferred enctypes ?!\n"); return 0; } for (n = 0; ktypes[n]; n++) /* count */ ; ksdata = calloc(n + 1, sizeof(struct krb_key_salt)); if (NULL == ksdata) { *err_msg = _("Out of memory!?\n"); return 0; } for (i = 0; i < n; i++) { ksdata[i].enctype = ktypes[i]; ksdata[i].salttype = KRB5_KDB_SALTTYPE_NORMAL; } ipa_krb5_free_ktypes(krbctx, ktypes); nkeys = i; } else { char *tmp, *t, *p, *q; t = tmp = strdup(str); if (!tmp) { *err_msg = _("Out of memory\n"); return 0; } /* count */ n = 0; while ((p = strchr(t, ','))) { t = p+1; n++; } n++; /* count the last one that is 0 terminated instead */ /* at the end we will have at most n entries + 1 terminating */ ksdata = calloc(n + 1, sizeof(struct krb_key_salt)); if (!ksdata) { *err_msg = _("Out of memory\n"); return 0; } for (i = 0, j = 0, t = tmp; i < n; i++) { p = strchr(t, ','); if (p) *p = '\0'; q = strchr(t, ':'); if (q) *q++ = '\0'; krberr = krb5_string_to_enctype(t, &ksdata[j].enctype); if (krberr != 0) { *err_msg = _("Warning unrecognized encryption type.\n"); if (p) t = p + 1; continue; } if (p) t = p + 1; if (!q) { ksdata[j].salttype = KRB5_KDB_SALTTYPE_NORMAL; j++; continue; } krberr = krb5_string_to_salttype(q, &ksdata[j].salttype); if (krberr != 0) { *err_msg = _("Warning unrecognized salt type.\n"); continue; } j++; } nkeys = j; free(tmp); } /* Check we don't already have a key with a similar encoding, * it would just produce redundant data and this is what the * MIT code do anyway */ for (i = 0, n = 0; i < nkeys; i++ ) { krb5_boolean similar = 0; for (j = 0; j < i; j++) { krberr = krb5_c_enctype_compare(krbctx, ksdata[j].enctype, ksdata[i].enctype, &similar); if (krberr) { free_keys_contents(krbctx, keys); free(ksdata); *err_msg = _("Enctype comparison failed!\n"); return 0; } if (similar && (ksdata[j].salttype == ksdata[i].salttype)) { break; } } if (j < i) { /* redundant encoding, remove it, and shift others */ int x; for (x = i; x < nkeys-1; x++) { ksdata[x].enctype = ksdata[x+1].enctype; ksdata[x].salttype = ksdata[x+1].salttype; } continue; } /* count only confirmed enc/salt tuples */ n++; } keys->nkeys = n; keys->ksdata = ksdata; return n; } int create_keys(krb5_context krbctx, krb5_principal princ, char *password, const char *enctypes_string, struct keys_container *keys, char **err_msg) { struct krb_key_salt *ksdata; krb5_error_code krberr; krb5_data key_password; krb5_data *realm = NULL; int i, nkeys; int ret; *err_msg = NULL; ret = prep_ksdata(krbctx, enctypes_string, keys, err_msg); if (ret == 0) return 0; ksdata = keys->ksdata; nkeys = keys->nkeys; if (password) { key_password.data = password; key_password.length = strlen(password); realm = krb5_princ_realm(krbctx, princ); } for (i = 0; i < nkeys; i++) { krb5_data *salt; if (!password) { /* cool, random keys */ krberr = krb5_c_make_random_key(krbctx, ksdata[i].enctype, &ksdata[i].key); if (krberr) { *err_msg = _("Failed to create random key!\n"); return 0; } /* set the salt to NO_SALT as the key was random */ ksdata[i].salttype = NO_SALT; continue; } /* Make keys using password and required salt */ switch (ksdata[i].salttype) { case KRB5_KDB_SALTTYPE_ONLYREALM: krberr = krb5_copy_data(krbctx, realm, &salt); if (krberr) { *err_msg = _("Failed to create key!\n"); return 0; } ksdata[i].salt.length = salt->length; ksdata[i].salt.data = malloc(salt->length); if (!ksdata[i].salt.data) { *err_msg = _("Out of memory!\n"); return 0; } memcpy(ksdata[i].salt.data, salt->data, salt->length); krb5_free_data(krbctx, salt); break; case KRB5_KDB_SALTTYPE_NOREALM: krberr = ipa_krb5_principal2salt_norealm(krbctx, princ, &ksdata[i].salt); if (krberr) { *err_msg = _("Failed to create key!\n"); return 0; } break; case KRB5_KDB_SALTTYPE_NORMAL: krberr = krb5_principal2salt(krbctx, princ, &ksdata[i].salt); if (krberr) { *err_msg = _("Failed to create key!\n"); return 0; } break; /* no KRB5_KDB_SALTTYPE_V4, we do not support krb v4 */ case KRB5_KDB_SALTTYPE_AFS3: /* Comment from MIT sources: * * Why do we do this? Well, the afs_mit_string_to_key * * needs to use strlen, and the realm is not NULL * * terminated.... */ ksdata[i].salt.data = (char *)malloc(realm->length + 1); if (NULL == ksdata[i].salt.data) { *err_msg = _("Out of memory!\n"); return 0; } memcpy((char *)ksdata[i].salt.data, (char *)realm->data, realm->length); ksdata[i].salt.data[realm->length] = '\0'; /* AFS uses a special length (UGLY) */ ksdata[i].salt.length = SALT_TYPE_AFS_LENGTH; break; default: *err_msg = _("Bad or unsupported salt type.\n"); /* FIXME: fprintf(stderr, _("Bad or unsupported salt type (%d)!\n"), ksdata[i].salttype); */ return 0; } krberr = krb5_c_string_to_key(krbctx, ksdata[i].enctype, &key_password, &ksdata[i].salt, &ksdata[i].key); if (krberr) { *err_msg = _("Failed to create key!\n"); return 0; } /* set back salt length to real value if AFS3 */ if (ksdata[i].salttype == KRB5_KDB_SALTTYPE_AFS3) { ksdata[i].salt.length = realm->length; } } return nkeys; }