/* * Copyright (c) 2015, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * 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. */ /* * PFK Key Cache * * Key Cache used internally in PFK. * The purpose of the cache is to save access time to QSEE * when loading the keys. * Currently the cache is the same size as the total number of keys that can * be loaded to ICE. Since this number is relatively small, the alghoritms for * cache eviction are simple, linear and based on last usage timestamp, i.e * the node that will be evicted is the one with the oldest timestamp. * Empty entries always have the oldest timestamp. * */ #include #include #include #include #include #include #include #include #include "pfk_kc.h" #include "pfk_ice.h" /** the first available index in ice engine */ #define PFK_KC_STARTING_INDEX 2 /** currently the only supported key and salt sizes */ #define PFK_KC_KEY_SIZE 32 #define PFK_KC_SALT_SIZE 32 /** Table size */ /* TODO replace by some constant from ice.h */ #define PFK_KC_TABLE_SIZE ((32) - (PFK_KC_STARTING_INDEX)) /** The maximum key and salt size */ #define PFK_MAX_KEY_SIZE PFK_KC_KEY_SIZE #define PFK_MAX_SALT_SIZE PFK_KC_SALT_SIZE static DEFINE_SPINLOCK(kc_lock); static bool kc_ready; struct kc_entry { unsigned char key[PFK_MAX_KEY_SIZE]; size_t key_size; unsigned char salt[PFK_MAX_SALT_SIZE]; size_t salt_size; u64 time_stamp; u32 key_index; }; static struct kc_entry kc_table[PFK_KC_TABLE_SIZE] = {{{0}, 0, {0}, 0, 0, 0} }; /** * pfk_min_time_entry() - update min time and update min entry * @min_time: pointer to current min_time, might be updated with new value * @time: time to compare minimum with * @min_entry: ptr to ptr to current min_entry, might be updated with * ptr to new entry * @entry: will be the new min_entry if the time was updated * * * Calculates the minimum between min_time and time. Replaces the min_time * if time is less and replaces min_entry with entry * */ static inline void pfk_min_time_entry(u64 *min_time, u64 time, struct kc_entry **min_entry, struct kc_entry *entry) { if (time_before64(time, *min_time)) { *min_time = time; *min_entry = entry; } } /** * kc_is_ready() - driver is initialized and ready. * * Return: true if the key cache is ready. */ static inline bool kc_is_ready(void) { return kc_ready == true; } /** * kc_find_key_at_index() - find kc entry starting at specific index * @key: key to look for * @key_size: the key size * @salt: salt to look for * @salt_size: the salt size * @sarting_index: index to start search with, if entry found, updated with * index of that entry * * Return entry or NULL in case of error * Should be invoked under lock */ static struct kc_entry *kc_find_key_at_index(const unsigned char *key, size_t key_size, const unsigned char *salt, size_t salt_size, int *starting_index) { struct kc_entry *entry = NULL; int i = 0; for (i = *starting_index; i < PFK_KC_TABLE_SIZE; i++) { entry = &(kc_table[i]); if (NULL != salt) { if (entry->salt_size != salt_size) continue; if (0 != memcmp(entry->salt, salt, salt_size)) continue; } if (entry->key_size != key_size) continue; if (0 == memcmp(entry->key, key, key_size)) { *starting_index = i; return entry; } } return NULL; } /** * kc_find_key() - find kc entry * @key: key to look for * @key_size: the key size * @salt: salt to look for * @salt_size: the salt size * * Return entry or NULL in case of error * Should be invoked under lock */ static struct kc_entry *kc_find_key(const unsigned char *key, size_t key_size, const unsigned char *salt, size_t salt_size) { int index = 0; return kc_find_key_at_index(key, key_size, salt, salt_size, &index); } /** * kc_find_oldest_entry() - finds the entry with minimal timestamp * * Returns entry with minimal timestamp. Empty entries have timestamp * of 0, therefore they are returned first. * Should always succeed, the returned entry should never be NULL * Should be invoked under lock */ static struct kc_entry *kc_find_oldest_entry(void) { struct kc_entry *curr_min_entry = NULL; struct kc_entry *entry = NULL; u64 min_time = 0; int i = 0; min_time = kc_table[0].time_stamp; curr_min_entry = &(kc_table[0]); for (i = 0; i < PFK_KC_TABLE_SIZE; i++) { entry = &(kc_table[i]); if (!entry->time_stamp) return entry; pfk_min_time_entry(&min_time, entry->time_stamp, &curr_min_entry, entry); } return curr_min_entry; } /** * kc_update_timestamp() - updates timestamp of entry to current * * @entry: entry to update * * If system time can't be retrieved, timestamp will not be updated * Should be invoked under lock */ static void kc_update_timestamp(struct kc_entry *entry) { if (!entry) return; entry->time_stamp = get_jiffies_64(); } /** * kc_clear_entry() - clear the key from entry and remove the key from ICE * * @entry: pointer to entry * * Securely wipe and release the key memory, remove the key from ICE * Should be invoked under lock */ static void kc_clear_entry(struct kc_entry *entry) { if (!entry) return; memset(entry->key, 0, entry->key_size); memset(entry->salt, 0, entry->salt_size); entry->time_stamp = 0; } /** * kc_replace_entry() - replaces the key in given entry and * loads the new key to ICE * * @entry: entry to replace key in * @key: key * @key_size: key_size * @salt: salt * @salt_size: salt_size * * The previous key is securely released and wiped, the new one is loaded * to ICE. * Should be invoked under lock */ static int kc_replace_entry(struct kc_entry *entry, const unsigned char *key, size_t key_size, const unsigned char *salt, size_t salt_size) { int ret = 0; kc_clear_entry(entry); memcpy(entry->key, key, key_size); entry->key_size = key_size; memcpy(entry->salt, salt, salt_size); entry->salt_size = salt_size; ret = qti_pfk_ice_set_key(entry->key_index, (uint8_t *) key, (uint8_t *) salt); if (ret != 0) { ret = -EINVAL; goto err; } kc_update_timestamp(entry); return 0; err: kc_clear_entry(entry); return ret; } /** * pfk_kc_init() - init function * * Return 0 in case of success, error otherwise */ int pfk_kc_init(void) { int i = 0; spin_lock(&kc_lock); for (i = 0; i < PFK_KC_TABLE_SIZE; i++) kc_table[i].key_index = PFK_KC_STARTING_INDEX + i; spin_unlock(&kc_lock); kc_ready = true; return 0; } /** * pfk_kc_denit() - deinit function * * Return 0 in case of success, error otherwise */ int pfk_kc_deinit(void) { pfk_kc_clear(); kc_ready = false; return 0; } /** * pfk_kc_load_key() - retrieve the key from cache or add it if it's not there * return the ICE hw key index * @key: pointer to the key * @key_size: the size of the key * @salt: pointer to the salt * @salt_size: the size of the salt * @key_index: the pointer to key_index where the output will be stored * * If key is present in cache, than the key_index will be retrieved from cache. * If it is not present, the oldest entry from kc table will be evicted, * the key will be loaded to ICE via QSEE to the index that is the evicted * entry number and stored in cache * * Return 0 in case of success, error otherwise */ int pfk_kc_load_key(const unsigned char *key, size_t key_size, const unsigned char *salt, size_t salt_size, u32 *key_index) { int ret = 0; struct kc_entry *entry = NULL; if (!kc_is_ready()) return -ENODEV; if (!key || !salt || !key_index) return -EPERM; if (key_size != PFK_KC_KEY_SIZE) return -EPERM; if (salt_size != PFK_KC_SALT_SIZE) return -EPERM; spin_lock(&kc_lock); entry = kc_find_key(key, key_size, salt, salt_size); if (!entry) { entry = kc_find_oldest_entry(); if (!entry) { pr_err("internal error, there should always be an oldest entry\n"); spin_unlock(&kc_lock); return -EINVAL; } pr_debug("didn't found key in cache, replacing entry with index %d\n", entry->key_index); ret = kc_replace_entry(entry, key, key_size, salt, salt_size); if (ret) { spin_unlock(&kc_lock); return -EINVAL; } } else { pr_debug("found key in cache, index %d\n", entry->key_index); kc_update_timestamp(entry); } *key_index = entry->key_index; spin_unlock(&kc_lock); return 0; } /** * pfk_kc_remove_key() - remove the key from cache and from ICE engine * @key: pointer to the key * @key_size: the size of the key * @salt: pointer to the key * @salt_size: the size of the key * * Return 0 in case of success, error otherwise (also in case of non * (existing key) */ int pfk_kc_remove_key_with_salt(const unsigned char *key, size_t key_size, const unsigned char *salt, size_t salt_size) { struct kc_entry *entry = NULL; if (!kc_is_ready()) return -ENODEV; if (!key) return -EPERM; if (!salt) return -EPERM; if (key_size != PFK_KC_KEY_SIZE) return -EPERM; if (salt_size != PFK_KC_SALT_SIZE) return -EPERM; spin_lock(&kc_lock); entry = kc_find_key(key, key_size, salt, salt_size); if (!entry) { pr_err("key does not exist\n"); spin_unlock(&kc_lock); return -EINVAL; } kc_clear_entry(entry); spin_unlock(&kc_lock); qti_pfk_ice_invalidate_key(entry->key_index); return 0; } /** * pfk_kc_remove_key() - remove the key from cache and from ICE engine * when no salt is available. Will only search key part, if there are several, * all will be removed * * @key: pointer to the key * @key_size: the size of the key * * Return 0 in case of success, error otherwise (also in case of non * (existing key) */ int pfk_kc_remove_key(const unsigned char *key, size_t key_size) { struct kc_entry *entry = NULL; int index = 0; int temp_indexes[PFK_KC_TABLE_SIZE] = {0}; int i = 0; if (!kc_is_ready()) return -ENODEV; if (!key) return -EPERM; if (key_size != PFK_KC_KEY_SIZE) return -EPERM; memset(temp_indexes, -1, sizeof(temp_indexes)); spin_lock(&kc_lock); entry = kc_find_key_at_index(key, key_size, NULL, 0, &index); if (!entry) { pr_debug("key does not exist\n"); spin_unlock(&kc_lock); return -EINVAL; } temp_indexes[i++] = entry->key_index; kc_clear_entry(entry); /* let's clean additional entries with the same key if there are any */ do { entry = kc_find_key_at_index(key, key_size, NULL, 0, &index); if (!entry) break; temp_indexes[i++] = entry->key_index; kc_clear_entry(entry); } while (true); spin_unlock(&kc_lock); for (i--; i >= 0 ; i--) qti_pfk_ice_invalidate_key(temp_indexes[i]); return 0; } /** * pfk_kc_clear() - clear the table and remove all keys from ICE * */ void pfk_kc_clear(void) { struct kc_entry *entry = NULL; int i = 0; if (!kc_is_ready()) return; spin_lock(&kc_lock); for (i = 0; i < PFK_KC_TABLE_SIZE; i++) { entry = &(kc_table[i]); kc_clear_entry(entry); } spin_unlock(&kc_lock); for (i = 0; i < PFK_KC_TABLE_SIZE; i++) qti_pfk_ice_invalidate_key(entry->key_index); }