static krb5_error_code add_enctype_to_key_set(Key **key_set, size_t *nkeyset, krb5_enctype enctype, krb5_salt *salt) { krb5_error_code ret; Key key, *tmp; memset(&key, 0, sizeof(key)); tmp = realloc(*key_set, (*nkeyset + 1) * sizeof((*key_set)[0])); if (tmp == NULL) return ENOMEM; *key_set = tmp; key.key.keytype = enctype; key.key.keyvalue.length = 0; key.key.keyvalue.data = NULL; if (salt) { key.salt = calloc(1, sizeof(*key.salt)); if (key.salt == NULL) { free_Key(&key); return ENOMEM; } key.salt->type = salt->salttype; krb5_data_zero (&key.salt->salt); ret = krb5_data_copy(&key.salt->salt, salt->saltvalue.data, salt->saltvalue.length); if (ret) { free_Key(&key); return ret; } } else key.salt = NULL; (*key_set)[*nkeyset] = key; *nkeyset += 1; return 0; }
void hdb_free_key(Key *key) { memset(key->key.keyvalue.data, 0, key->key.keyvalue.length); free_Key(key); free(key); }
kadm5_ret_t kadm5_log_replay_modify (kadm5_server_context *context, u_int32_t ver, u_int32_t len, krb5_storage *sp) { krb5_error_code ret; int32_t mask; krb5_data value; hdb_entry ent, log_ent; krb5_ret_int32 (sp, &mask); len -= 4; ret = krb5_data_alloc (&value, len); if (ret) return ret; krb5_storage_read (sp, value.data, len); ret = hdb_value2entry (context->context, &value, &log_ent); krb5_data_free(&value); if (ret) return ret; ent.principal = log_ent.principal; log_ent.principal = NULL; ret = context->db->hdb_fetch(context->context, context->db, HDB_F_DECRYPT, &ent); if (ret) return ret; if (mask & KADM5_PRINC_EXPIRE_TIME) { if (log_ent.valid_end == NULL) { ent.valid_end = NULL; } else { if (ent.valid_end == NULL) ent.valid_end = malloc(sizeof(*ent.valid_end)); *ent.valid_end = *log_ent.valid_end; } } if (mask & KADM5_PW_EXPIRATION) { if (log_ent.pw_end == NULL) { ent.pw_end = NULL; } else { if (ent.pw_end == NULL) ent.pw_end = malloc(sizeof(*ent.pw_end)); *ent.pw_end = *log_ent.pw_end; } } if (mask & KADM5_LAST_PWD_CHANGE) { abort (); /* XXX */ } if (mask & KADM5_ATTRIBUTES) { ent.flags = log_ent.flags; } if (mask & KADM5_MAX_LIFE) { if (log_ent.max_life == NULL) { ent.max_life = NULL; } else { if (ent.max_life == NULL) ent.max_life = malloc (sizeof(*ent.max_life)); *ent.max_life = *log_ent.max_life; } } if ((mask & KADM5_MOD_TIME) && (mask & KADM5_MOD_NAME)) { if (ent.modified_by == NULL) { ent.modified_by = malloc(sizeof(*ent.modified_by)); } else free_Event(ent.modified_by); copy_Event(log_ent.modified_by, ent.modified_by); } if (mask & KADM5_KVNO) { ent.kvno = log_ent.kvno; } if (mask & KADM5_MKVNO) { abort (); /* XXX */ } if (mask & KADM5_AUX_ATTRIBUTES) { abort (); /* XXX */ } if (mask & KADM5_POLICY) { abort (); /* XXX */ } if (mask & KADM5_POLICY_CLR) { abort (); /* XXX */ } if (mask & KADM5_MAX_RLIFE) { if (log_ent.max_renew == NULL) { ent.max_renew = NULL; } else { if (ent.max_renew == NULL) ent.max_renew = malloc (sizeof(*ent.max_renew)); *ent.max_renew = *log_ent.max_renew; } } if (mask & KADM5_LAST_SUCCESS) { abort (); /* XXX */ } if (mask & KADM5_LAST_FAILED) { abort (); /* XXX */ } if (mask & KADM5_FAIL_AUTH_COUNT) { abort (); /* XXX */ } if (mask & KADM5_KEY_DATA) { size_t len; int i; for (i = 0; i < ent.keys.len; ++i) free_Key(&ent.keys.val[i]); free (ent.keys.val); len = log_ent.keys.len; ent.keys.len = len; ent.keys.val = malloc(len * sizeof(*ent.keys.val)); for (i = 0; i < ent.keys.len; ++i) copy_Key(&log_ent.keys.val[i], &ent.keys.val[i]); } ret = context->db->hdb_store(context->context, context->db, HDB_F_REPLACE, &ent); hdb_free_entry (context->context, &ent); hdb_free_entry (context->context, &log_ent); return ret; }
static kadm5_ret_t kadm5_log_replay_modify (kadm5_server_context *context, uint32_t ver, uint32_t len, krb5_storage *sp) { krb5_error_code ret; int32_t mask; krb5_data value; hdb_entry_ex ent, log_ent; memset(&log_ent, 0, sizeof(log_ent)); krb5_ret_int32 (sp, &mask); len -= 4; ret = krb5_data_alloc (&value, len); if (ret) { krb5_set_error_message(context->context, ret, "out of memory"); return ret; } krb5_storage_read (sp, value.data, len); ret = hdb_value2entry (context->context, &value, &log_ent.entry); krb5_data_free(&value); if (ret) return ret; memset(&ent, 0, sizeof(ent)); ret = context->db->hdb_fetch_kvno(context->context, context->db, log_ent.entry.principal, HDB_F_DECRYPT|HDB_F_GET_ANY|HDB_F_ADMIN_DATA, 0, &ent); if (ret) goto out; if (mask & KADM5_PRINC_EXPIRE_TIME) { if (log_ent.entry.valid_end == NULL) { ent.entry.valid_end = NULL; } else { if (ent.entry.valid_end == NULL) { ent.entry.valid_end = malloc(sizeof(*ent.entry.valid_end)); if (ent.entry.valid_end == NULL) { ret = ENOMEM; krb5_set_error_message(context->context, ret, "out of memory"); goto out; } } *ent.entry.valid_end = *log_ent.entry.valid_end; } } if (mask & KADM5_PW_EXPIRATION) { if (log_ent.entry.pw_end == NULL) { ent.entry.pw_end = NULL; } else { if (ent.entry.pw_end == NULL) { ent.entry.pw_end = malloc(sizeof(*ent.entry.pw_end)); if (ent.entry.pw_end == NULL) { ret = ENOMEM; krb5_set_error_message(context->context, ret, "out of memory"); goto out; } } *ent.entry.pw_end = *log_ent.entry.pw_end; } } if (mask & KADM5_LAST_PWD_CHANGE) { abort (); /* XXX */ } if (mask & KADM5_ATTRIBUTES) { ent.entry.flags = log_ent.entry.flags; } if (mask & KADM5_MAX_LIFE) { if (log_ent.entry.max_life == NULL) { ent.entry.max_life = NULL; } else { if (ent.entry.max_life == NULL) { ent.entry.max_life = malloc (sizeof(*ent.entry.max_life)); if (ent.entry.max_life == NULL) { ret = ENOMEM; krb5_set_error_message(context->context, ret, "out of memory"); goto out; } } *ent.entry.max_life = *log_ent.entry.max_life; } } if ((mask & KADM5_MOD_TIME) && (mask & KADM5_MOD_NAME)) { if (ent.entry.modified_by == NULL) { ent.entry.modified_by = malloc(sizeof(*ent.entry.modified_by)); if (ent.entry.modified_by == NULL) { ret = ENOMEM; krb5_set_error_message(context->context, ret, "out of memory"); goto out; } } else free_Event(ent.entry.modified_by); ret = copy_Event(log_ent.entry.modified_by, ent.entry.modified_by); if (ret) { krb5_set_error_message(context->context, ret, "out of memory"); goto out; } } if (mask & KADM5_KVNO) { ent.entry.kvno = log_ent.entry.kvno; } if (mask & KADM5_MKVNO) { abort (); /* XXX */ } if (mask & KADM5_AUX_ATTRIBUTES) { abort (); /* XXX */ } if (mask & KADM5_POLICY) { abort (); /* XXX */ } if (mask & KADM5_POLICY_CLR) { abort (); /* XXX */ } if (mask & KADM5_MAX_RLIFE) { if (log_ent.entry.max_renew == NULL) { ent.entry.max_renew = NULL; } else { if (ent.entry.max_renew == NULL) { ent.entry.max_renew = malloc (sizeof(*ent.entry.max_renew)); if (ent.entry.max_renew == NULL) { ret = ENOMEM; krb5_set_error_message(context->context, ret, "out of memory"); goto out; } } *ent.entry.max_renew = *log_ent.entry.max_renew; } } if (mask & KADM5_LAST_SUCCESS) { abort (); /* XXX */ } if (mask & KADM5_LAST_FAILED) { abort (); /* XXX */ } if (mask & KADM5_FAIL_AUTH_COUNT) { abort (); /* XXX */ } if (mask & KADM5_KEY_DATA) { size_t num; int i; for (i = 0; i < ent.entry.keys.len; ++i) free_Key(&ent.entry.keys.val[i]); free (ent.entry.keys.val); num = log_ent.entry.keys.len; ent.entry.keys.len = num; ent.entry.keys.val = malloc(len * sizeof(*ent.entry.keys.val)); if (ent.entry.keys.val == NULL) { krb5_set_error_message(context->context, ENOMEM, "out of memory"); return ENOMEM; } for (i = 0; i < ent.entry.keys.len; ++i) { ret = copy_Key(&log_ent.entry.keys.val[i], &ent.entry.keys.val[i]); if (ret) { krb5_set_error_message(context->context, ret, "out of memory"); goto out; } } } if ((mask & KADM5_TL_DATA) && log_ent.entry.extensions) { HDB_extensions *es = ent.entry.extensions; ent.entry.extensions = calloc(1, sizeof(*ent.entry.extensions)); if (ent.entry.extensions == NULL) goto out; ret = copy_HDB_extensions(log_ent.entry.extensions, ent.entry.extensions); if (ret) { krb5_set_error_message(context->context, ret, "out of memory"); free(ent.entry.extensions); ent.entry.extensions = es; goto out; } if (es) { free_HDB_extensions(es); free(es); } } ret = context->db->hdb_store(context->context, context->db, HDB_F_REPLACE, &ent); out: hdb_free_entry (context->context, &ent); hdb_free_entry (context->context, &log_ent); return ret; }
krb5_error_code hdb_generate_key_set(krb5_context context, krb5_principal principal, Key **ret_key_set, size_t *nkeyset, int no_salt) { char **ktypes, **kp; krb5_error_code ret; Key *k, *key_set; size_t i, j; static const char *default_keytypes[] = { "aes256-cts-hmac-sha1-96:pw-salt", "des3-cbc-sha1:pw-salt", "arcfour-hmac-md5:pw-salt", NULL }; ktypes = krb5_config_get_strings(context, NULL, "kadmin", "default_keys", NULL); if (ktypes == NULL) ktypes = (char **)(intptr_t)default_keytypes; *ret_key_set = key_set = NULL; *nkeyset = 0; ret = 0; for(kp = ktypes; kp && *kp; kp++) { const char *p; krb5_salt salt; krb5_enctype *enctypes; size_t num_enctypes; p = *kp; /* check alias */ if(strcmp(p, "v5") == 0) p = "pw-salt"; else if(strcmp(p, "v4") == 0) p = "des:pw-salt:"; else if(strcmp(p, "afs") == 0 || strcmp(p, "afs3") == 0) p = "des:afs3-salt"; else if (strcmp(p, "arcfour-hmac-md5") == 0) p = "arcfour-hmac-md5:pw-salt"; memset(&salt, 0, sizeof(salt)); ret = parse_key_set(context, p, &enctypes, &num_enctypes, &salt, principal); if (ret) { krb5_warn(context, ret, "bad value for default_keys `%s'", *kp); ret = 0; continue; } for (i = 0; i < num_enctypes; i++) { /* find duplicates */ for (j = 0; j < *nkeyset; j++) { k = &key_set[j]; if (k->key.keytype == enctypes[i]) { if (no_salt) break; if (k->salt == NULL && salt.salttype == KRB5_PW_SALT) break; if (k->salt->type == salt.salttype && k->salt->salt.length == salt.saltvalue.length && memcmp(k->salt->salt.data, salt.saltvalue.data, salt.saltvalue.length) == 0) break; } } /* not a duplicate, lets add it */ if (j == *nkeyset) { ret = add_enctype_to_key_set(&key_set, nkeyset, enctypes[i], no_salt ? NULL : &salt); if (ret) { free(enctypes); krb5_free_salt(context, salt); goto out; } } } free(enctypes); krb5_free_salt(context, salt); } *ret_key_set = key_set; out: if (ktypes != (char **)(intptr_t)default_keytypes) krb5_config_free_strings(ktypes); if (ret) { krb5_warn(context, ret, "failed to parse the [kadmin]default_keys values"); for (i = 0; i < *nkeyset; i++) free_Key(&key_set[i]); free(key_set); } else if (*nkeyset == 0) { krb5_warnx(context, "failed to parse any of the [kadmin]default_keys values"); ret = EINVAL; /* XXX */ } return ret; }
static kadm5_ret_t kadm5_log_replay_modify (kadm5_server_context *context, uint32_t ver, uint32_t len, krb5_storage *sp) { krb5_error_code ret; int32_t mask; krb5_data value; hdb_entry_ex ent, log_ent; memset(&log_ent, 0, sizeof(log_ent)); krb5_ret_int32 (sp, &mask); len -= 4; ret = krb5_data_alloc (&value, len); if (ret) { krb5_set_error_message(context->context, ret, "out of memory"); return ret; } krb5_storage_read (sp, value.data, len); ret = hdb_value2entry (context->context, &value, &log_ent.entry); krb5_data_free(&value); if (ret) return ret; memset(&ent, 0, sizeof(ent)); ret = context->db->hdb_fetch_kvno(context->context, context->db, log_ent.entry.principal, HDB_F_DECRYPT|HDB_F_ALL_KVNOS| HDB_F_GET_ANY|HDB_F_ADMIN_DATA, 0, &ent); if (ret) goto out; if (mask & KADM5_PRINC_EXPIRE_TIME) { if (log_ent.entry.valid_end == NULL) { ent.entry.valid_end = NULL; } else { if (ent.entry.valid_end == NULL) { ent.entry.valid_end = malloc(sizeof(*ent.entry.valid_end)); if (ent.entry.valid_end == NULL) { ret = ENOMEM; krb5_set_error_message(context->context, ret, "out of memory"); goto out; } } *ent.entry.valid_end = *log_ent.entry.valid_end; } } if (mask & KADM5_PW_EXPIRATION) { if (log_ent.entry.pw_end == NULL) { ent.entry.pw_end = NULL; } else { if (ent.entry.pw_end == NULL) { ent.entry.pw_end = malloc(sizeof(*ent.entry.pw_end)); if (ent.entry.pw_end == NULL) { ret = ENOMEM; krb5_set_error_message(context->context, ret, "out of memory"); goto out; } } *ent.entry.pw_end = *log_ent.entry.pw_end; } } if (mask & KADM5_LAST_PWD_CHANGE) { abort (); /* XXX */ } if (mask & KADM5_ATTRIBUTES) { ent.entry.flags = log_ent.entry.flags; } if (mask & KADM5_MAX_LIFE) { if (log_ent.entry.max_life == NULL) { ent.entry.max_life = NULL; } else { if (ent.entry.max_life == NULL) { ent.entry.max_life = malloc (sizeof(*ent.entry.max_life)); if (ent.entry.max_life == NULL) { ret = ENOMEM; krb5_set_error_message(context->context, ret, "out of memory"); goto out; } } *ent.entry.max_life = *log_ent.entry.max_life; } } if ((mask & KADM5_MOD_TIME) && (mask & KADM5_MOD_NAME)) { if (ent.entry.modified_by == NULL) { ent.entry.modified_by = malloc(sizeof(*ent.entry.modified_by)); if (ent.entry.modified_by == NULL) { ret = ENOMEM; krb5_set_error_message(context->context, ret, "out of memory"); goto out; } } else free_Event(ent.entry.modified_by); ret = copy_Event(log_ent.entry.modified_by, ent.entry.modified_by); if (ret) { krb5_set_error_message(context->context, ret, "out of memory"); goto out; } } if (mask & KADM5_KVNO) { ent.entry.kvno = log_ent.entry.kvno; } if (mask & KADM5_MKVNO) { abort (); /* XXX */ } if (mask & KADM5_AUX_ATTRIBUTES) { abort (); /* XXX */ } if (mask & KADM5_POLICY) { abort (); /* XXX */ } if (mask & KADM5_POLICY_CLR) { abort (); /* XXX */ } if (mask & KADM5_MAX_RLIFE) { if (log_ent.entry.max_renew == NULL) { ent.entry.max_renew = NULL; } else { if (ent.entry.max_renew == NULL) { ent.entry.max_renew = malloc (sizeof(*ent.entry.max_renew)); if (ent.entry.max_renew == NULL) { ret = ENOMEM; krb5_set_error_message(context->context, ret, "out of memory"); goto out; } } *ent.entry.max_renew = *log_ent.entry.max_renew; } } if (mask & KADM5_LAST_SUCCESS) { abort (); /* XXX */ } if (mask & KADM5_LAST_FAILED) { abort (); /* XXX */ } if (mask & KADM5_FAIL_AUTH_COUNT) { abort (); /* XXX */ } if (mask & KADM5_KEY_DATA) { size_t num; size_t i; /* * We don't need to do anything about key history here because * we always log KADM5_TL_DATA when we change keys/passwords, so * the code below this will handle key history implicitly. * However, if we had to, the code to handle key history here * would look like this: * * HDB_extension *ext; * ... * ext = hdb_find_extension(&log_ent.entry, * choice_HDB_extension_data_hist_keys); * if (ext); * ret = hdb_replace_extension(context->context, &ent.entry, ext); * else * ret = hdb_clear_extension(context->context, &ent.entry, * choice_HDB_extension_data_hist_keys); * * Maybe we should do this here anyways, wasteful as it would * be, as a defensive programming measure? For now we heim_assert(). */ heim_assert((mask & KADM5_TL_DATA), "Wouldn't log and replay key history"); for (i = 0; i < ent.entry.keys.len; ++i) free_Key(&ent.entry.keys.val[i]); free (ent.entry.keys.val); num = log_ent.entry.keys.len; ent.entry.keys.len = num; ent.entry.keys.val = malloc(len * sizeof(*ent.entry.keys.val)); if (ent.entry.keys.val == NULL) { krb5_set_error_message(context->context, ENOMEM, "out of memory"); return ENOMEM; } for (i = 0; i < ent.entry.keys.len; ++i) { ret = copy_Key(&log_ent.entry.keys.val[i], &ent.entry.keys.val[i]); if (ret) { krb5_set_error_message(context->context, ret, "out of memory"); goto out; } } } if ((mask & KADM5_TL_DATA) && log_ent.entry.extensions) { HDB_extensions *es = ent.entry.extensions; ent.entry.extensions = calloc(1, sizeof(*ent.entry.extensions)); if (ent.entry.extensions == NULL) goto out; ret = copy_HDB_extensions(log_ent.entry.extensions, ent.entry.extensions); if (ret) { krb5_set_error_message(context->context, ret, "out of memory"); free(ent.entry.extensions); ent.entry.extensions = es; goto out; } if (es) { free_HDB_extensions(es); free(es); } } ret = context->db->hdb_store(context->context, context->db, HDB_F_REPLACE, &ent); out: hdb_free_entry (context->context, &ent); hdb_free_entry (context->context, &log_ent); return ret; }
static krb5_error_code mdb_value2entry(krb5_context context, krb5_data *data, hdb_entry *entry) { krb5_error_code ret; krb5_storage *sp; uint32_t u32; uint16_t u16, num_keys, num_tl; size_t i, j; char *p = NULL; sp = krb5_storage_from_data(data); if (sp == NULL) { krb5_set_error_message(context, ENOMEM, "out of memory"); return ENOMEM; } krb5_storage_set_byteorder(sp, KRB5_STORAGE_BYTEORDER_LE); /* 16: baselength */ CHECK(ret = krb5_ret_uint16(sp, &u16)); if (u16 != KDB_V1_BASE_LENGTH) { ret = EINVAL; goto out; } /* 32: attributes */ CHECK(ret = krb5_ret_uint32(sp, &u32)); entry->flags.postdate = !(u16 & KRB5_KDB_DISALLOW_POSTDATED); entry->flags.forwardable = !(u16 & KRB5_KDB_DISALLOW_FORWARDABLE); entry->flags.initial = !!(u16 & KRB5_KDB_DISALLOW_TGT_BASED); entry->flags.renewable = !(u16 & KRB5_KDB_DISALLOW_RENEWABLE); entry->flags.proxiable = !(u16 & KRB5_KDB_DISALLOW_PROXIABLE); /* DUP_SKEY */ entry->flags.invalid = !!(u16 & KRB5_KDB_DISALLOW_ALL_TIX); entry->flags.require_preauth =!!(u16 & KRB5_KDB_REQUIRES_PRE_AUTH); entry->flags.require_hwauth =!!(u16 & KRB5_KDB_REQUIRES_HW_AUTH); entry->flags.server = !(u16 & KRB5_KDB_DISALLOW_SVR); entry->flags.change_pw = !!(u16 & KRB5_KDB_PWCHANGE_SERVICE); entry->flags.client = 1; /* XXX */ /* 32: max time */ CHECK(ret = krb5_ret_uint32(sp, &u32)); if (u32) { entry->max_life = malloc(sizeof(*entry->max_life)); *entry->max_life = u32; } /* 32: max renewable time */ CHECK(ret = krb5_ret_uint32(sp, &u32)); if (u32) { entry->max_renew = malloc(sizeof(*entry->max_renew)); *entry->max_renew = u32; } /* 32: client expire */ CHECK(ret = krb5_ret_uint32(sp, &u32)); if (u32) { entry->valid_end = malloc(sizeof(*entry->valid_end)); *entry->valid_end = u32; } /* 32: passwd expire */ CHECK(ret = krb5_ret_uint32(sp, &u32)); if (u32) { entry->pw_end = malloc(sizeof(*entry->pw_end)); *entry->pw_end = u32; } /* 32: last successful passwd */ CHECK(ret = krb5_ret_uint32(sp, &u32)); /* 32: last failed attempt */ CHECK(ret = krb5_ret_uint32(sp, &u32)); /* 32: num of failed attempts */ CHECK(ret = krb5_ret_uint32(sp, &u32)); /* 16: num tl data */ CHECK(ret = krb5_ret_uint16(sp, &u16)); num_tl = u16; /* 16: num key data */ CHECK(ret = krb5_ret_uint16(sp, &u16)); num_keys = u16; /* 16: principal length */ CHECK(ret = krb5_ret_uint16(sp, &u16)); /* length: principal */ { p = malloc(u16 + 1); krb5_storage_read(sp, p, u16); p[u16] = '\0'; CHECK(ret = krb5_parse_name(context, p, &entry->principal)); free(p); p = NULL; } /* for num tl data times 16: tl data type 16: tl data length length: length */ for (i = 0; i < num_tl; i++) { CHECK(ret = krb5_ret_uint16(sp, &u16)); CHECK(ret = krb5_ret_uint16(sp, &u16)); krb5_storage_seek(sp, u16, SEEK_CUR); } /* for num key data times 16: version (num keyblocks) 16: kvno for version times: 16: type 16: length length: keydata */ for (i = 0; i < num_keys; i++) { int keep = 0; uint16_t version; void *ptr; CHECK(ret = krb5_ret_uint16(sp, &u16)); version = u16; CHECK(ret = krb5_ret_uint16(sp, &u16)); if (entry->kvno < u16) { keep = 1; entry->kvno = u16; for (j = 0; j < entry->keys.len; j++) { free_Key(&entry->keys.val[j]); free(entry->keys.val); entry->keys.len = 0; entry->keys.val = NULL; } } else if (entry->kvno == u16) keep = 1; if (keep) { Key *k; ptr = realloc(entry->keys.val, sizeof(entry->keys.val[0]) * (entry->keys.len + 1)); if (ptr == NULL) { ret = ENOMEM; goto out; } entry->keys.val = ptr; /* k points to current Key */ k = &entry->keys.val[entry->keys.len]; memset(k, 0, sizeof(*k)); entry->keys.len += 1; entry->keys.val[i].mkvno = malloc(sizeof(*entry->keys.val[i].mkvno)); if (entry->keys.val[i].mkvno == NULL) { ret = ENOMEM; goto out; } *entry->keys.val[i].mkvno = 1; for (j = 0; j < version; j++) { uint16_t type; CHECK(ret = krb5_ret_uint16(sp, &type)); CHECK(ret = krb5_ret_uint16(sp, &u16)); if (j == 0) { /* key */ k->key.keytype = type; if (u16 < 2) { ret = EINVAL; goto out; } krb5_storage_seek(sp, 2, SEEK_CUR); /* skip real length */ k->key.keyvalue.length = u16 - 2; k->key.keyvalue.data = malloc(k->key.keyvalue.length); krb5_storage_read(sp, k->key.keyvalue.data, k->key.keyvalue.length); } else if (j == 1) { /* salt */ k->salt = calloc(1, sizeof(*k->salt)); if (k->salt == NULL) { ret = ENOMEM; goto out; } k->salt->type = type; if (u16 != 0) { k->salt->salt.data = malloc(u16); if (k->salt->salt.data == NULL) { ret = ENOMEM; goto out; } k->salt->salt.length = u16; krb5_storage_read(sp, k->salt->salt.data, k->salt->salt.length); } fix_salt(context, entry, entry->keys.len - 1); } else { krb5_storage_seek(sp, u16, SEEK_CUR); } } } else { /* skip */ for (j = 0; j < version; j++) { CHECK(ret = krb5_ret_uint16(sp, &u16)); CHECK(ret = krb5_ret_uint16(sp, &u16)); krb5_storage_seek(sp, u16, u16); } } } return 0; out: if (p) free(p); free_hdb_entry(entry); return ret; }
static krb5_error_code mdb_value2entry(krb5_context context, krb5_data *data, krb5_kvno kvno, hdb_entry *entry) { krb5_error_code ret; krb5_storage *sp; uint32_t u32; uint16_t u16, num_keys, num_tl; size_t i, j; char *p; sp = krb5_storage_from_data(data); if (sp == NULL) { krb5_set_error_message(context, ENOMEM, "out of memory"); return ENOMEM; } krb5_storage_set_byteorder(sp, KRB5_STORAGE_BYTEORDER_LE); /* * 16: baselength * * The story here is that these 16 bits have to be a constant: * KDB_V1_BASE_LENGTH. Once upon a time a different value here * would have been used to indicate the presence of "extra data" * between the "base" contents and the {principal name, TL data, * keys} that follow it. Nothing supports such "extra data" * nowadays, so neither do we here. * * XXX But... surely we ought to log about this extra data, or skip * it, or something, in case anyone has MIT KDBs with ancient * entries in them... Logging would allow the admin to know which * entries to dump with MIT krb5's kdb5_util. */ CHECK(ret = krb5_ret_uint16(sp, &u16)); if (u16 != KDB_V1_BASE_LENGTH) { ret = EINVAL; goto out; } /* 32: attributes */ CHECK(ret = krb5_ret_uint32(sp, &u32)); entry->flags.postdate = !(u32 & KRB5_KDB_DISALLOW_POSTDATED); entry->flags.forwardable = !(u32 & KRB5_KDB_DISALLOW_FORWARDABLE); entry->flags.initial = !!(u32 & KRB5_KDB_DISALLOW_TGT_BASED); entry->flags.renewable = !(u32 & KRB5_KDB_DISALLOW_RENEWABLE); entry->flags.proxiable = !(u32 & KRB5_KDB_DISALLOW_PROXIABLE); /* DUP_SKEY */ entry->flags.invalid = !!(u32 & KRB5_KDB_DISALLOW_ALL_TIX); entry->flags.require_preauth =!!(u32 & KRB5_KDB_REQUIRES_PRE_AUTH); entry->flags.require_hwauth =!!(u32 & KRB5_KDB_REQUIRES_HW_AUTH); entry->flags.server = !(u32 & KRB5_KDB_DISALLOW_SVR); entry->flags.change_pw = !!(u32 & KRB5_KDB_PWCHANGE_SERVICE); entry->flags.client = 1; /* XXX */ /* 32: max time */ CHECK(ret = krb5_ret_uint32(sp, &u32)); if (u32) { entry->max_life = malloc(sizeof(*entry->max_life)); *entry->max_life = u32; } /* 32: max renewable time */ CHECK(ret = krb5_ret_uint32(sp, &u32)); if (u32) { entry->max_renew = malloc(sizeof(*entry->max_renew)); *entry->max_renew = u32; } /* 32: client expire */ CHECK(ret = krb5_ret_uint32(sp, &u32)); if (u32) { entry->valid_end = malloc(sizeof(*entry->valid_end)); *entry->valid_end = u32; } /* 32: passwd expire */ CHECK(ret = krb5_ret_uint32(sp, &u32)); if (u32) { entry->pw_end = malloc(sizeof(*entry->pw_end)); *entry->pw_end = u32; } /* 32: last successful passwd */ CHECK(ret = krb5_ret_uint32(sp, &u32)); /* 32: last failed attempt */ CHECK(ret = krb5_ret_uint32(sp, &u32)); /* 32: num of failed attempts */ CHECK(ret = krb5_ret_uint32(sp, &u32)); /* 16: num tl data */ CHECK(ret = krb5_ret_uint16(sp, &u16)); num_tl = u16; /* 16: num key data */ CHECK(ret = krb5_ret_uint16(sp, &u16)); num_keys = u16; /* 16: principal length */ CHECK(ret = krb5_ret_uint16(sp, &u16)); /* length: principal */ { /* * Note that the principal name includes the NUL in the entry, * but we don't want to take chances, so we add an extra NUL. */ p = malloc(u16 + 1); if (p == NULL) { ret = ENOMEM; goto out; } krb5_storage_read(sp, p, u16); p[u16] = '\0'; CHECK(ret = krb5_parse_name(context, p, &entry->principal)); free(p); } /* for num tl data times 16: tl data type 16: tl data length length: length */ for (i = 0; i < num_tl; i++) { /* 16: TL data type */ CHECK(ret = krb5_ret_uint16(sp, &u16)); /* 16: TL data length */ CHECK(ret = krb5_ret_uint16(sp, &u16)); krb5_storage_seek(sp, u16, SEEK_CUR); } /* * for num key data times * 16: "version" * 16: kvno * for version times: * 16: type * 16: length * length: keydata * * "version" here is really 1 or 2, the first meaning there's only * keys for this kvno, the second meaning there's keys and salt[s?]. * That's right... hold that gag reflex, you can do it. */ for (i = 0; i < num_keys; i++) { int keep = 0; uint16_t version; void *ptr; CHECK(ret = krb5_ret_uint16(sp, &u16)); version = u16; CHECK(ret = krb5_ret_uint16(sp, &u16)); /* * First time through, and until we find one matching key, * entry->kvno == 0. */ if ((entry->kvno < u16) && (kvno == 0 || kvno == u16)) { keep = 1; entry->kvno = u16; /* * Found a higher kvno than earlier, so free the old highest * kvno keys. * * XXX Of course, we actually want to extract the old kvnos * as well, for some of the kadm5 APIs. We shouldn't free * these keys, but keep them elsewhere. */ for (j = 0; j < entry->keys.len; j++) free_Key(&entry->keys.val[j]); free(entry->keys.val); entry->keys.len = 0; entry->keys.val = NULL; } else if (entry->kvno == u16) /* Accumulate keys */ keep = 1; if (keep) { Key *k; ptr = realloc(entry->keys.val, sizeof(entry->keys.val[0]) * (entry->keys.len + 1)); if (ptr == NULL) { ret = ENOMEM; goto out; } entry->keys.val = ptr; /* k points to current Key */ k = &entry->keys.val[entry->keys.len]; memset(k, 0, sizeof(*k)); entry->keys.len += 1; k->mkvno = malloc(sizeof(*k->mkvno)); if (k->mkvno == NULL) { ret = ENOMEM; goto out; } *k->mkvno = 1; for (j = 0; j < version; j++) { uint16_t type; CHECK(ret = krb5_ret_uint16(sp, &type)); CHECK(ret = krb5_ret_uint16(sp, &u16)); if (j == 0) { /* This "version" means we have a key */ k->key.keytype = type; if (u16 < 2) { ret = EINVAL; goto out; } /* * MIT stores keys encrypted keys as {16-bit length * of plaintext key, {encrypted key}}. The reason * for this is that the Kerberos cryptosystem is not * length-preserving. Heimdal's approach is to * truncate the plaintext to the expected length of * the key given its enctype, so we ignore this * 16-bit length-of-plaintext-key field. */ krb5_storage_seek(sp, 2, SEEK_CUR); /* skip real length */ k->key.keyvalue.length = u16 - 2; /* adjust cipher len */ k->key.keyvalue.data = malloc(k->key.keyvalue.length); krb5_storage_read(sp, k->key.keyvalue.data, k->key.keyvalue.length); } else if (j == 1) { /* This "version" means we have a salt */ k->salt = calloc(1, sizeof(*k->salt)); if (k->salt == NULL) { ret = ENOMEM; goto out; } k->salt->type = type; if (u16 != 0) { k->salt->salt.data = malloc(u16); if (k->salt->salt.data == NULL) { ret = ENOMEM; goto out; } k->salt->salt.length = u16; krb5_storage_read(sp, k->salt->salt.data, k->salt->salt.length); } fix_salt(context, entry, entry->keys.len - 1); } else { /* * Whatever this "version" might be, we skip it * * XXX A krb5.conf parameter requesting that we log * about strangeness like this, or return an error * from here, might be nice. */ krb5_storage_seek(sp, u16, SEEK_CUR); } } } else { /* * XXX For now we skip older kvnos, but we should extract * them... */ for (j = 0; j < version; j++) { /* enctype */ CHECK(ret = krb5_ret_uint16(sp, &u16)); /* encrypted key (or plaintext salt) */ CHECK(ret = krb5_ret_uint16(sp, &u16)); krb5_storage_seek(sp, u16, SEEK_CUR); } } } if (entry->kvno == 0 && kvno != 0) { ret = HDB_ERR_NOT_FOUND_HERE; goto out; } return 0; out: if (ret == HEIM_ERR_EOF) /* Better error code than "end of file" */ ret = HEIM_ERR_BAD_HDBENT_ENCODING; return ret; }