diff --git a/applications/debug/unit_tests/storage/storage_test.c b/applications/debug/unit_tests/storage/storage_test.c index 7c1c669ff4..61a32808d8 100644 --- a/applications/debug/unit_tests/storage/storage_test.c +++ b/applications/debug/unit_tests/storage/storage_test.c @@ -1,6 +1,7 @@ #include "../minunit.h" #include #include +#include #define STORAGE_LOCKED_FILE EXT_PATH("locked_file.test") #define STORAGE_LOCKED_DIR STORAGE_INT_PATH_PREFIX @@ -309,9 +310,37 @@ MU_TEST_SUITE(storage_rename) { furi_record_close(RECORD_STORAGE); } +MU_TEST(storage_encrypt_file) { + Storage* storage = furi_record_open(RECORD_STORAGE); + File* file; + const char* path = EXT_PATH("enc_file"); + const uint8_t key_slot = 0; + + file = storage_file_alloc(storage); + mu_check(storage_file_open(file, path, FSAM_READ_WRITE, FSOM_CREATE_ALWAYS)); + + const char* content = "Lorem ipsum dolor sit amet, cons"; + mu_check(storage_file_write(file, content, 32) == 32); + storage_file_free(file); + + mu_assert_int_eq(FSE_OK, storage_file_encrypt(storage, path, key_slot)); + mu_check(storage_file_is_encrypted(storage, path)); + + furi_record_close(RECORD_STORAGE); +} + +MU_TEST_SUITE(storage_encrypt_decrypt) { + MU_RUN_TEST(storage_encrypt_file); + + Storage* storage = furi_record_open(RECORD_STORAGE); + storage_dir_remove(storage, EXT_PATH("enc_file")); + furi_record_close(RECORD_STORAGE); +} + int run_minunit_test_storage() { MU_RUN_SUITE(storage_file); MU_RUN_SUITE(storage_dir); MU_RUN_SUITE(storage_rename); + MU_RUN_SUITE(storage_encrypt_decrypt); return MU_EXIT_CODE; } diff --git a/applications/services/storage/filesystem_api.c b/applications/services/storage/filesystem_api.c index b979967acb..79acd2ced3 100644 --- a/applications/services/storage/filesystem_api.c +++ b/applications/services/storage/filesystem_api.c @@ -33,6 +33,9 @@ const char* filesystem_api_error_get_desc(FS_Error error_id) { case(FSE_ALREADY_OPEN): result = "file is already open"; break; + case(FSE_NO_SPACE): + result = "no free space"; + break; } return result; } diff --git a/applications/services/storage/filesystem_api_defines.h b/applications/services/storage/filesystem_api_defines.h index b6f1d8f130..c6f8c32ea1 100644 --- a/applications/services/storage/filesystem_api_defines.h +++ b/applications/services/storage/filesystem_api_defines.h @@ -33,6 +33,7 @@ typedef enum { FSE_INTERNAL, /**< Internal error */ FSE_NOT_IMPLEMENTED, /**< Functon not implemented */ FSE_ALREADY_OPEN, /**< File/Dir already opened */ + FSE_NO_SPACE, /**< No free space available */ } FS_Error; /** FileInfo flags */ diff --git a/applications/services/storage/filesystem_api_internal.h b/applications/services/storage/filesystem_api_internal.h index bd4bcf823a..01a53ebd85 100644 --- a/applications/services/storage/filesystem_api_internal.h +++ b/applications/services/storage/filesystem_api_internal.h @@ -6,6 +6,27 @@ extern "C" { #endif +#define ENCRYPTION_IV_SIZE 16 +#define ENCRYPTION_EXT ".enc" + +/** Magic bytes to detect if file is encrypted. + * Encrypted file bytes layout is as follows: + * --------------- ----------------- ---------------- ----------------------- + * | 7 bytes magic | 1 byte key slot | 16 byte vector | actual encrypted data | + * --------------- ----------------- ---------------- ----------------------- + */ +static const uint8_t encryption_magic_bytes[7] = {0x46, 0x45, 0x46, 0x2e, 0x41, 0x45, 0x53}; +static const size_t encryption_magic_size = + sizeof(encryption_magic_bytes) / sizeof(encryption_magic_bytes[0]); +static const size_t encryption_header_size = + encryption_magic_size + sizeof(uint8_t) + ENCRYPTION_IV_SIZE; + +/** Structure that holds file encryption info */ +typedef struct FileEncryption { + bool decrypted; /**< Current encryption state */ + uint8_t key_slot; /**< Slot number of encryption key */ +} FileEncryption; + /** File type */ typedef enum { FileTypeClosed, /**< Closed file */ @@ -17,9 +38,11 @@ typedef enum { struct File { uint32_t file_id; /**< File ID for internal references */ FileType type; + const char* path; /**< Actual path of opened file */ FS_Error error_id; /**< Standart API error from FS_Error enum */ int32_t internal_error_id; /**< Internal API error value */ void* storage; + FileEncryption* encryption; }; /** File api structure diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h index 9c133e9be1..e394aa1e3d 100644 --- a/applications/services/storage/storage.h +++ b/applications/services/storage/storage.h @@ -135,6 +135,39 @@ bool storage_file_sync(File* file); */ bool storage_file_eof(File* file); +/** Checks that file at given path is encrypted + * @param storage + * @param path path to file. + * @return bool success flag + */ +bool storage_file_is_encrypted(Storage* storage, const char* path); + +/** Checks that file is decrypted at the moment + * @param file pointer to file object. + * @return bool success flag + */ +bool storage_file_is_decrypted(File* file); + +/** Reports if encryption is enabled for this file + * @param file pointer to file object. + * @return bool success flag + */ +bool storage_file_secured(File* file); + +/** Decrypts previously encrypted file + * @param file pointer to file object. + * @return FS_Error operation result + */ +FS_Error storage_file_decrypt(File* file); + +/** Encrypts file using key from given secure enclave slot + * @param storage + * @param path path to file. + * @param key_slot slot of key in secure enclave. + * @return FS_Error operation result + */ +FS_Error storage_file_encrypt(Storage* storage, const char* path, uint8_t key_slot); + /** * @brief Check that file exists * diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c index 2c3a7bfc9b..c4fe649d67 100644 --- a/applications/services/storage/storage_external_api.c +++ b/applications/services/storage/storage_external_api.c @@ -259,6 +259,29 @@ bool storage_file_exists(Storage* storage, const char* path) { return exist; } +FS_Error storage_file_decrypt(File* file) { + if(storage_file_is_decrypted(file)) { + return FSE_OK; + } + + S_FILE_API_PROLOGUE; + S_API_PROLOGUE; + S_API_DATA_FILE; + S_API_MESSAGE(StorageCommandFileDecrypt); + S_API_EPILOGUE; + return S_RETURN_ERROR; +} + +FS_Error storage_file_encrypt(Storage* storage, const char* path, uint8_t key_slot) { + S_API_PROLOGUE; + + SAData data = {.encryption = {.path = path, .key_slot = key_slot}}; + + S_API_MESSAGE(StorageCommandFileEncrypt); + S_API_EPILOGUE; + return S_RETURN_ERROR; +} + /****************** DIR ******************/ static bool storage_dir_open_internal(File* file, const char* path) { @@ -714,6 +737,26 @@ bool storage_file_is_dir(File* file) { return (file->type == FileTypeOpenDir); } +bool storage_file_is_encrypted(Storage* storage, const char* path) { + Stream* fstream = file_stream_alloc(storage); + if(!file_stream_open(fstream, path, FSAM_READ, FSOM_OPEN_EXISTING)) { + stream_free(fstream); + return false; + }; + uint8_t magic_buf[encryption_magic_size]; + stream_read(fstream, magic_buf, encryption_magic_size); + stream_free(fstream); + return memcmp(&encryption_magic_bytes, &magic_buf, encryption_magic_size) == 0; +} + +bool storage_file_is_decrypted(File* file) { + return file->encryption && file->encryption->decrypted; +} + +bool storage_file_secured(File* file) { + return file->encryption; +} + void storage_file_free(File* file) { if(storage_file_is_open(file)) { if(storage_file_is_dir(file)) { @@ -724,6 +767,7 @@ void storage_file_free(File* file) { } FURI_LOG_T(TAG, "File/Dir %p free", (void*)((uint32_t)file - SRAM_BASE)); + free(file->encryption); free(file); } diff --git a/applications/services/storage/storage_message.h b/applications/services/storage/storage_message.h index 9872680179..9518b64a89 100644 --- a/applications/services/storage/storage_message.h +++ b/applications/services/storage/storage_message.h @@ -70,6 +70,11 @@ typedef struct { File* file; } SADataFile; +typedef struct { + const char* path; + const uint8_t key_slot; +} SADataEncryption; + typedef struct { SDInfo* info; } SAInfo; @@ -91,6 +96,7 @@ typedef union { SADataFile file; SADataPath path; + SADataEncryption encryption; SAInfo sdinfo; } SAData; @@ -114,6 +120,8 @@ typedef enum { StorageCommandFileSize, StorageCommandFileSync, StorageCommandFileEof, + StorageCommandFileEncrypt, + StorageCommandFileDecrypt, StorageCommandDirOpen, StorageCommandDirClose, StorageCommandDirRead, diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index 795a5d11c8..a28914036d 100644 --- a/applications/services/storage/storage_processing.c +++ b/applications/services/storage/storage_processing.c @@ -1,6 +1,7 @@ #include "storage_processing.h" #include #include +#include #define FS_CALL(_storage, _fn) \ storage_data_lock(_storage); \ @@ -118,7 +119,9 @@ bool storage_process_file_open( storage_data_timestamp(storage); } storage_push_storage_file(file, real_path, type, storage); + file->path = furi_string_get_cstr(real_path); FS_CALL(storage, file.open(storage, file, remove_vfs(path), access_mode, open_mode)); + //storage_file_decrypt(file); } furi_string_free(real_path); @@ -139,6 +142,10 @@ bool storage_process_file_close(Storage* app, File* file) { StorageEvent event = {.type = StorageEventTypeFileClose}; furi_pubsub_publish(app->pubsub, &event); + + if(file->encryption && file->encryption->decrypted) { + //storage_process_file_encrypt(app, file->path, file->encryption->key_slot); + } } return ret; @@ -260,6 +267,141 @@ static bool storage_process_file_eof(Storage* app, File* file) { return ret; } +static FileEncryption* storage_file_encryption_alloc(const uint8_t key_slot) { + FileEncryption data = (FileEncryption){ + .decrypted = false, + .key_slot = key_slot, + }; + + FileEncryption* file_encryption = malloc(sizeof(FileEncryption)); + memcpy(file_encryption, &data, sizeof(FileEncryption)); + return file_encryption; +} + +static FS_Error storage_process_file_decrypt(Storage* app, File* file) { + // file already decrypted + if(file->encryption && file->encryption->decrypted) { + return FSE_OK; + } + // file is not big enough to be encrypted + if(storage_process_file_size(app, file) <= encryption_header_size) { + return FSE_OK; + } + + // check if file is encrypted by reading magic bytes + // cursor needs to be at the beginning of file + uint8_t magic_buf[encryption_magic_size]; + storage_process_file_read(app, file, &magic_buf, encryption_magic_size); + if(memcmp(encryption_magic_bytes, magic_buf, encryption_magic_size) != 0) { + // file is not encrypted, move cursor back and stop + storage_process_file_seek(app, file, -1 * encryption_magic_size, false); + return FSE_OK; + } + + // read key slot id and initialization vector + uint8_t key_slot; + uint8_t iv[ENCRYPTION_IV_SIZE]; + storage_file_read(file, &key_slot, sizeof(key_slot)); + storage_file_read(file, &iv, ENCRYPTION_IV_SIZE); + + // store encryption info + file->encryption = storage_file_encryption_alloc(key_slot); + + // decrypt file + if(!furi_hal_crypto_store_load_key(key_slot, iv)) { + return FSE_INTERNAL; + } + // we will decrypt file in-place, so we need two cursors: + // decryption cursor - points to next encrypted chunk of data, always in front of data cursor + uint64_t dec_cursor = storage_process_file_tell(app, file); + // decrypted data cursor - points to the end of decrypted data + uint64_t data_cursor = 0; + + uint8_t chunk_size = 64; + uint8_t crypt_buf[chunk_size]; + uint8_t dec_buf[chunk_size]; + while(!storage_process_file_eof(app, file)) { + storage_process_file_seek(app, file, dec_cursor, true); + uint16_t read = storage_process_file_read(app, file, &crypt_buf, chunk_size); + dec_cursor += read; + furi_hal_crypto_decrypt(crypt_buf, (uint8_t*)&dec_buf, read); + storage_process_file_seek(app, file, data_cursor, true); + data_cursor += storage_process_file_write(app, file, dec_buf, read); + } + furi_hal_crypto_store_unload_key(key_slot); + + // truncate exceeding data and reset cursor + storage_process_file_seek(app, file, data_cursor, true); + storage_process_file_truncate(app, file); + storage_process_file_seek(app, file, 0, true); + + file->encryption->decrypted = true; + return FSE_OK; +} + +static FS_Error + storage_process_file_encrypt(Storage* app, const char* path, const uint8_t key_slot) { + FS_Error error = FSE_OK; + + Stream* stream_from = file_stream_alloc(app); + Stream* stream_to = file_stream_alloc(app); + + const char* enc_filepath = strcat((char*)path, ENCRYPTION_EXT); + + if(file_stream_open(stream_from, path, FSAM_READ, FSOM_OPEN_EXISTING) && + file_stream_open(stream_to, enc_filepath, FSAM_WRITE, FSOM_CREATE_NEW)) { + // check free space + uint64_t total_space; + uint64_t free_space; + storage_common_fs_info(app, path, &total_space, &free_space); + + if(free_space > stream_size(stream_from) + encryption_header_size) { + // create new random initialization vector + uint8_t iv[ENCRYPTION_IV_SIZE]; + srand(DWT->CYCCNT); + furi_hal_random_fill_buf(iv, ENCRYPTION_IV_SIZE); + + // write header to encrypted file + stream_write(stream_to, encryption_magic_bytes, encryption_magic_size); + stream_write(stream_to, &key_slot, sizeof(key_slot)); + stream_write(stream_to, iv, ENCRYPTION_IV_SIZE); + + // start encryption + if(furi_hal_crypto_store_load_key(key_slot, iv)) { + uint8_t chunk_size = 16; + uint8_t data_buf[chunk_size]; + uint8_t enc_buf[chunk_size]; + + while(!stream_eof(stream_from)) { + memset(data_buf, 0, chunk_size); + stream_read(stream_from, data_buf, chunk_size); + furi_hal_crypto_encrypt(data_buf, enc_buf, chunk_size); + stream_write(stream_to, enc_buf, chunk_size); + } + + furi_hal_crypto_store_unload_key(key_slot); + + // swap files + storage_common_remove(app, path); + storage_common_rename(app, enc_filepath, path); + } else { + error = FSE_INVALID_PARAMETER; + } + } else { + error = FSE_NO_SPACE; + } + } else { + error = file_stream_get_error(stream_from); + if(error == FSE_OK) { + error = file_stream_get_error(stream_to); + } + } + + stream_free(stream_from); + stream_free(stream_to); + return error; +} + /******************* Dir Functions *******************/ bool storage_process_dir_open(Storage* app, File* file, const char* path) { @@ -545,6 +687,14 @@ void storage_process_message_internal(Storage* app, StorageMessage* message) { case StorageCommandFileEof: message->return_data->bool_value = storage_process_file_eof(app, message->data->file.file); break; + case StorageCommandFileEncrypt: + message->return_data->error_value = storage_process_file_encrypt( + app, message->data->encryption.path, message->data->encryption.key_slot); + break; + case StorageCommandFileDecrypt: + message->return_data->error_value = + storage_process_file_decrypt(app, message->data->file.file); + break; case StorageCommandDirOpen: message->return_data->bool_value = diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 14f8983fab..22069547cc 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,7.51,, +Version,+,7.52,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2391,16 +2391,21 @@ Function,-,storage_dir_rewind,_Bool,File* Function,+,storage_error_get_desc,const char*,FS_Error Function,+,storage_file_alloc,File*,Storage* Function,+,storage_file_close,_Bool,File* +Function,+,storage_file_decrypt,FS_Error,File* +Function,+,storage_file_encrypt,FS_Error,"Storage*, const char*, uint8_t" Function,+,storage_file_eof,_Bool,File* Function,+,storage_file_exists,_Bool,"Storage*, const char*" Function,+,storage_file_free,void,File* Function,+,storage_file_get_error,FS_Error,File* Function,+,storage_file_get_error_desc,const char*,File* Function,-,storage_file_get_internal_error,int32_t,File* +Function,+,storage_file_is_decrypted,_Bool,File* Function,+,storage_file_is_dir,_Bool,File* +Function,+,storage_file_is_encrypted,_Bool,"Storage*, const char*" Function,+,storage_file_is_open,_Bool,File* Function,+,storage_file_open,_Bool,"File*, const char*, FS_AccessMode, FS_OpenMode" Function,+,storage_file_read,uint16_t,"File*, void*, uint16_t" +Function,+,storage_file_secured,_Bool,File* Function,+,storage_file_seek,_Bool,"File*, uint32_t, _Bool" Function,+,storage_file_size,uint64_t,File* Function,-,storage_file_sync,_Bool,File*