| /* |
| * 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 <linux/mutex.h> |
| #include <linux/spinlock.h> |
| #include <crypto/ice.h> |
| #include <linux/errno.h> |
| #include <linux/string.h> |
| #include <linux/jiffies.h> |
| #include <linux/slab.h> |
| #include <linux/printk.h> |
| |
| #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); |
| |
| } |
| |