/* * Copyright (c) 2016-2019 Samsung Electronics Co., Ltd. All rights reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License * * * @file key_handler.c * @author Dongsun Lee (ds73.lee@samsung.com) * @version 1.0 * @brief Key manupulatation. */ #include "key_handler.h" #include #include #include #include #include #include #include #include "web_app_enc.h" #include "wae_log.h" #include "crypto_service.h" #include "key_manager.h" #include "decrypt_migrated_wgt.h" #define RANDOM_FILE "/dev/urandom" #define APP_DEK_KEK_PRIKEY_PASSWORD "wae_appdek_kek_1q2w3e4r" #define APP_DEK_FILE_PFX "WAE_APP_DEK" #define DEK_LEN 32 #define IV_LEN 16 #define MAX_PKGID_LEN 256 #define MAX_CACHE_SIZE 100 static unsigned char AES_CBC_IV[IV_LEN] = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x08, 0x39, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; static crypto_element_map_s *_map; static void deinit_lib(void) __attribute__((destructor)); static void deinit_lib(void) { crypto_element_map_destroy(_map); } char *_create_map_key(uid_t uid, const char *pkg_id) { char *key = NULL; int ret = asprintf(&key, "%u-%s", uid, pkg_id); return (ret == -1) ? NULL : key; } static const crypto_element_s *_get_app_ce_from_cache(const char *key) { return crypto_element_map_get(_map, key); } static int _add_app_ce_to_cache(const char *key, crypto_element_s *ce) { return crypto_element_map_add(&_map, key, ce); } void _remove_app_ce_from_cache(const char *key) { crypto_element_map_remove(&_map, key); } int _get_random(raw_buffer_s *rb) { if (!is_buffer_valid(rb)) return WAE_ERROR_INVALID_PARAMETER; FILE *f = fopen(RANDOM_FILE, "r"); if (f == NULL) { WAE_SLOGE("Failed to open random file source: %s", RANDOM_FILE); return WAE_ERROR_FILE; } size_t i = 0; int ch = 0; while ((i < rb->size) && ((ch = fgetc(f)) != EOF)) rb->buf[i++] = (unsigned char)ch; fclose(f); return WAE_ERROR_NONE; } const char *_get_dek_store_path() { return tzplatform_mkpath3(TZ_SYS_SHARE, "wae", "app_dek"); } static int _write_to_file(const char *path, const raw_buffer_s *data) { if (path == NULL || !is_buffer_valid(data)) return WAE_ERROR_INVALID_PARAMETER; FILE *f = fopen(path, "w"); if (f == NULL) { WAE_SLOGE("Failed to open a file(%s)", path); return WAE_ERROR_FILE; } int write_len = fwrite(data->buf, 1, data->size, f); fclose(f); if (write_len != (int)data->size) { WAE_SLOGE("Failed to write a file(%s)", path); return WAE_ERROR_FILE; } return WAE_ERROR_NONE; } static int _read_from_file(const char *path, raw_buffer_s **pdata) { int ret = WAE_ERROR_NONE; raw_buffer_s *data = NULL; int ch = 0; int i = 0; FILE *f = fopen(path, "r"); if (f == NULL) { WAE_SLOGE("Failed to open a file. file=%s", path); return WAE_ERROR_FILE; } if (fseek(f, 0, SEEK_END) != 0) { // move to the end of a file WAE_SLOGE("Failed in fseek. file=%s", path); ret = WAE_ERROR_FILE; goto error; } int file_len = ftell(f); if (file_len <= 0) { WAE_SLOGE("Failed to get file size by ftell. ret: %d", file_len); ret = WAE_ERROR_FILE; goto error; } if (fseek(f, 0, SEEK_SET) != 0) { // move to the start of a file WAE_SLOGE("Failed in fseek. file=%s", path); ret = WAE_ERROR_FILE; goto error; } data = buffer_create(file_len); if (data == NULL) { WAE_SLOGE("Failed to allocate memory for encrypted_dek"); ret = WAE_ERROR_MEMORY; goto error; } while ((ch = fgetc(f)) != EOF) data->buf[i++] = (char)ch; *pdata = data; error: fclose(f); if (ret != WAE_ERROR_NONE) buffer_destroy(data); return ret; } typedef int(*entry_callback)(const char *path, const struct dirent *entry); static int traverse_directory(const char *path, entry_callback ecb) { DIR *dir = opendir(path); if (dir == NULL) { if (errno == ENOENT) { // it's not error for current cases of using traverse_directory. // To open dek store directory for load/remove can be occured in some // exception(or attacked) cases but we can just ignore it if it isn't the // first time call load_preloaded_app_deks. WAE_SLOGI("directory isn't exist(%s).", path); return WAE_ERROR_NONE; } else { WAE_SLOGE("Failed to open dir(%s)", path); return WAE_ERROR_FILE; } } int ret = WAE_ERROR_NONE; while (true) { errno = 0; struct dirent *result = readdir(dir); if (result == NULL) { if (errno != 0) WAE_SLOGE("readdir error on dir(%s) errno(%d)", path, errno); break; } else if (strcmp(result->d_name, ".") == 0 || strcmp(result->d_name, "..") == 0) { continue; } int _ret = ecb(path, result); if (_ret != WAE_ERROR_NONE) ret = _ret; } closedir(dir); return ret; } static void _remove_file(const char *path) { unlink(path); } static int _entry_callback_remove_all( const char *path, const struct dirent *entry) { char file_path_buff[MAX_PATH_LEN] = {0, }; if ((unsigned)snprintf(file_path_buff, sizeof(file_path_buff), "%s/%s", path, entry->d_name) >= sizeof(file_path_buff)) return WAE_ERROR_INVALID_PARAMETER; /* buffer size too small */ int ret = WAE_ERROR_NONE; if (entry->d_type == DT_DIR) { int _ret = traverse_directory(file_path_buff, _entry_callback_remove_all); if (_ret != WAE_ERROR_NONE) ret = _ret; rmdir(file_path_buff); } else { _remove_file(file_path_buff); } return ret; } void _remove_directory(const char *path) { traverse_directory(path, _entry_callback_remove_all); WAE_SLOGD("remove directory(%s)", path); rmdir(path); } int _get_preloaded_app_dek_file_path(const char *pkg_id, size_t size, char *path) { if ((unsigned)snprintf(path, size, "%s/%s_%s.adek", _get_dek_store_path(), APP_DEK_FILE_PFX, pkg_id) >= size) return WAE_ERROR_INVALID_PARAMETER; /* buffer size too small */ else return WAE_ERROR_NONE; } static int _extract_pkg_id_from_file_name(const char *file_name, char *pkg_id) { char *start = strstr(file_name, APP_DEK_FILE_PFX); if (start == NULL) { WAE_SLOGE("WAE: Fail to extract pkgid from APP_DEK file. file_name=%s", file_name); return WAE_ERROR_FILE; } start = start + strlen(APP_DEK_FILE_PFX) + 1; char *end = strstr(file_name, ".adek"); if (end == NULL) { WAE_SLOGE("WAE: Fail to extract pkgid from APP_DEK file. file_name=%s", file_name); return WAE_ERROR_FILE; } if (end - start >= MAX_PKGID_LEN) { WAE_SLOGE("WAE: pkgid extracted from APP_DEK file too long. file_name=%s", file_name); return WAE_ERROR_INVALID_PARAMETER; } strncpy(pkg_id, start, end - start); pkg_id[end - start] = 0; //terminate string return WAE_ERROR_NONE; } int _read_encrypted_app_dek_from_file(const char *pkg_id, raw_buffer_s **pencrypted) { char path[MAX_PATH_LEN] = {0,}; _get_preloaded_app_dek_file_path(pkg_id, sizeof(path), path); return _read_from_file(path, pencrypted); } int _write_encrypted_app_dek_to_file(const char *pkg_id, const raw_buffer_s *encrypted) { char path[MAX_PATH_LEN] = {0,}; _get_preloaded_app_dek_file_path(pkg_id, sizeof(path), path); return _write_to_file(path, encrypted); } int _load_preloaded_app_dek(const char *filepath, const char *pkg_id) { raw_buffer_s *encrypted_dek = NULL; raw_buffer_s *dek = NULL; raw_buffer_s *iv = NULL; crypto_element_s *ce = NULL; int ret = _read_from_file(filepath, &encrypted_dek); if (ret != WAE_ERROR_NONE) { WAE_SLOGW("Failed to read file. It will be ignored. file=%s", filepath); return ret; } ret = decrypt_preloaded_app_dek(encrypted_dek, &dek); if (ret != WAE_ERROR_NONE) { WAE_SLOGW("Failed to decrypt dek. It will be ignored. file=%s", filepath); goto finish; } iv = buffer_create(IV_LEN); if (iv == NULL) { ret = WAE_ERROR_MEMORY; goto finish; } memcpy(iv->buf, AES_CBC_IV, iv->size); ce = crypto_element_create(dek, iv); if (ce == NULL) { ret = WAE_ERROR_MEMORY; goto finish; } ret = save_to_key_manager(pkg_id, pkg_id, WAE_PRELOADED_APP, ce); if (ret == WAE_ERROR_KEY_EXISTS) { WAE_SLOGI("Key Manager already has dek. It will be ignored. file=%s", filepath); } else if (ret != WAE_ERROR_NONE) { WAE_SLOGW("Fail to add APP DEK to key-manager. file=%s", filepath); } finish: buffer_destroy(encrypted_dek); if (ce == NULL) { buffer_destroy(dek); buffer_destroy(iv); } else { crypto_element_destroy(ce); } return ret; } int get_app_ce(uid_t uid, const char *pkg_id, wae_app_type_e app_type, bool create_for_migrated_app, const crypto_element_s **pce) { if (pkg_id == NULL || pce == NULL) return WAE_ERROR_INVALID_PARAMETER; if (uid == 0 && app_type == WAE_DOWNLOADED_NORMAL_APP) return WAE_ERROR_INVALID_PARAMETER; const char *key = NULL; char *_key_per_user = NULL; if (app_type == WAE_DOWNLOADED_NORMAL_APP) { _key_per_user = _create_map_key(uid, pkg_id); if (_key_per_user == NULL) return WAE_ERROR_MEMORY; key = _key_per_user; } else { key = pkg_id; } int ret = WAE_ERROR_NONE; const crypto_element_s *cached_ce = _get_app_ce_from_cache(key); if (cached_ce != NULL) { WAE_SLOGD("cache hit of app ce for key(%s)", key); *pce = cached_ce; goto finish; } WAE_SLOGD("cache miss of app ce for key(%s)", key); crypto_element_s *ce = NULL; ret = get_from_key_manager(key, app_type, &ce); if (create_for_migrated_app && (ret == WAE_ERROR_NO_KEY && app_type == WAE_DOWNLOADED_GLOBAL_APP)) { WAE_SLOGI("No dek found for key(%s)! It should be migrated app.", key); if ((ret = get_old_ss_crypto_element(key, &ce)) != WAE_ERROR_NONE) goto finish; // (k.tak) disable to save ce to key-maanger for migrated app because of permission issue. //ret = save_to_key_manager(key, pkg_id, app_type, ce); //if (ret != WAE_ERROR_NONE) { // WAE_SLOGW("Failed to save migrated app ce to key-manager with ret(%d). " // "Ignore this error because we can create ce later again.", ret); // ret = WAE_ERROR_NONE; //} } else if (ret != WAE_ERROR_NONE) { WAE_SLOGE("Failed to get crypto element from key-manager. key(%s) ret(%d)", key, ret); goto finish; } ret = _add_app_ce_to_cache(key, ce); if (ret != WAE_ERROR_NONE) { WAE_SLOGE("Failed to add ce to cache for key(%s) ret(%d)", key, ret); goto finish; } *pce = ce; WAE_SLOGD("Successfully get ce! key(%s)", key); finish: free(_key_per_user); if (ret != WAE_ERROR_NONE) crypto_element_destroy(ce); return ret; } int create_app_ce(uid_t uid, const char *pkg_id, wae_app_type_e app_type, const crypto_element_s **pce) { raw_buffer_s *dek = buffer_create(DEK_LEN); raw_buffer_s *iv = buffer_create(IV_LEN); crypto_element_s *ce = crypto_element_create(dek, iv); int ret = WAE_ERROR_NONE; const char *key = NULL; char *_key_per_user = NULL; if (ce == NULL) { ret = WAE_ERROR_MEMORY; goto error; } if (app_type == WAE_DOWNLOADED_NORMAL_APP) { _key_per_user = _create_map_key(uid, pkg_id); if (_key_per_user == NULL) { ret = WAE_ERROR_MEMORY; goto error; } key = _key_per_user; } else { key = pkg_id; } memcpy(ce->iv->buf, AES_CBC_IV, ce->iv->size); ret = _get_random(dek); if (ret != WAE_ERROR_NONE) { WAE_SLOGE("Failed to get random for dek. key(%s) ret(%d)", key, ret); goto error; } ret = save_to_key_manager(key, pkg_id, app_type, ce); if (ret != WAE_ERROR_NONE) { WAE_SLOGE("Failed to save ce to key-manager. key(%s) app_type(%d) ret(%d)", key, app_type, ret); goto error; } ret = _add_app_ce_to_cache(key, ce); if (ret != WAE_ERROR_NONE) { WAE_SLOGE("Failed to add ce to cache for key(%s) ret(%d)", key, ret); goto error; } *pce = ce; WAE_SLOGI("Success to create dek/iv and store it in key-manager. key(%s)", key); error: if (ret != WAE_ERROR_NONE) { if (ce == NULL) { buffer_destroy(dek); buffer_destroy(iv); } else { crypto_element_destroy(ce); } } free(_key_per_user); return ret; } int get_preloaded_app_ce(const char *pkg_id, const crypto_element_s **pce) { const crypto_element_s *cached_ce = _get_app_ce_from_cache(pkg_id); if (cached_ce == NULL) { WAE_SLOGE("WAE: Fail to get APP_DEK from cache for preloaded app"); return WAE_ERROR_NO_KEY; } *pce = cached_ce; return WAE_ERROR_NONE; } int create_preloaded_app_ce(const char *pkg_id, const crypto_element_s **pce) { raw_buffer_s *encrypted_app_dek = NULL; raw_buffer_s *dek = buffer_create(DEK_LEN); raw_buffer_s *iv = buffer_create(sizeof(AES_CBC_IV)); crypto_element_s *ce = crypto_element_create(dek, iv); int ret = WAE_ERROR_NONE; if (dek == NULL || iv == NULL || ce == NULL) { ret = WAE_ERROR_MEMORY; goto error; } ret = _get_random(dek); if (ret != WAE_ERROR_NONE) goto error; // copy default iv for preloaded app memcpy(iv->buf, AES_CBC_IV, sizeof(AES_CBC_IV)); ret = encrypt_preloaded_app_dek(dek, &encrypted_app_dek); if (ret != WAE_ERROR_NONE) { WAE_SLOGE("WAE: Fail to encrypt APP_DEK with APP_DEK_KEK"); goto error; } // write APP_DEK in a file ret = _write_encrypted_app_dek_to_file(pkg_id, encrypted_app_dek); if (ret != WAE_ERROR_NONE) { WAE_SLOGE("Failed to write encrypted dek to file. pkg_id(%s)", pkg_id); goto error; } // store APP_DEK in cache ret = _add_app_ce_to_cache(pkg_id, ce); if (ret != WAE_ERROR_NONE) { WAE_SLOGE("Failed to add ce to cache for pkg_id(%s) ret(%d)", pkg_id, ret); goto error; } *pce = ce; WAE_SLOGI("Success to create preleaded dek and write it in initial value file. " "pkg_id(%s)", pkg_id); error: buffer_destroy(encrypted_app_dek); if (ret != WAE_ERROR_NONE) { if (ce) { crypto_element_destroy(ce); } else { buffer_destroy(dek); buffer_destroy(iv); } } return ret; } static int _entry_callback_load_preloaded_adeks( const char *path, const struct dirent *entry) { char file_path_buff[MAX_PATH_LEN] = {0, }; if ((unsigned)snprintf(file_path_buff, sizeof(file_path_buff), "%s/%s", path, entry->d_name) >= sizeof(file_path_buff)) return WAE_ERROR_INVALID_PARAMETER; /* buffer size too small */ if (entry->d_type != DT_REG || strstr(entry->d_name, APP_DEK_FILE_PFX) == NULL) { if (entry->d_type == DT_DIR) WAE_SLOGW( "Invalid file in dek store(%s). Directory shouldn't be here.", path); else WAE_SLOGW( "Invalid file in dek store(%s). " "Not regular file or prefix(%s) is invalid.", path, APP_DEK_FILE_PFX); return WAE_ERROR_FILE; } char pkg_id[MAX_PKGID_LEN] = {0, }; int ret = _extract_pkg_id_from_file_name(entry->d_name, pkg_id); if (ret != WAE_ERROR_NONE) { WAE_SLOGW("Failed to extract pkgid from file(%s). It will be ignored.", file_path_buff); return ret; } ret = _load_preloaded_app_dek(file_path_buff, pkg_id); if (ret == WAE_ERROR_NONE || ret == WAE_ERROR_KEY_EXISTS) { WAE_SLOGI("Successfully load app dek(%s)", file_path_buff); return WAE_ERROR_NONE; } else { WAE_SLOGW("Failed to load app dek(%s) ret(%d)", file_path_buff, ret); return ret; } } int load_preloaded_app_deks() { WAE_SLOGI("load_preloaded_app_deks start"); int ret = WAE_ERROR_NONE; const char *dek_store_path = _get_dek_store_path(); DIR *dir = NULL; // check if all deks were already loaded into key-manager. // TODO: instead of checking key-manager, check based on file existance dir = opendir(dek_store_path); if (dir == NULL) { if (errno == ENOENT) { WAE_SLOGI( "dek store doesn't exist. " "It might be loading preloaded app deks already done"); return WAE_ERROR_NONE; } else { WAE_SLOGE("Fail to open dir. dir=%s", dek_store_path); ret = WAE_ERROR_FILE; goto out; } } // close dek store dir fd not to affect the traverse_directory call closedir(dir); ret = traverse_directory(dek_store_path, _entry_callback_load_preloaded_adeks); if (ret != WAE_ERROR_NONE) WAE_SLOGE("Fail when traverse dek store directory. ret(%d)", ret); out: _remove_directory(dek_store_path); return ret; } int remove_app_ce(uid_t uid, const char *pkg_id, wae_app_type_e app_type) { if (uid == 0 && app_type == WAE_DOWNLOADED_NORMAL_APP) return WAE_ERROR_INVALID_PARAMETER; const char *key = NULL; char *_key_per_user = NULL; if (app_type == WAE_DOWNLOADED_NORMAL_APP) { _key_per_user = _create_map_key(uid, pkg_id); if (_key_per_user == NULL) return WAE_ERROR_MEMORY; key = _key_per_user; } else { key = pkg_id; } int ret = remove_from_key_manager(key, app_type); if (ret != WAE_ERROR_NONE) WAE_SLOGE("Failed to remove app ce for key(%s) ret(%d)", key, ret); else WAE_SLOGI("Success to remove app ce for key(%s)", key); _remove_app_ce_from_cache(key); free(_key_per_user); return ret; }