Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
mbed-cloud-client/factory-configurator-client/mbed-client-esfs/source/esfs.c
- Committer:
- MACRUM
- Date:
- 2018-07-02
- Revision:
- 0:276e7a263c35
File content as of revision 0:276e7a263c35:
/* * Copyright (c) 2016 ARM Limited. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * 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. */ // ----------------------------------------------------------- Includes ----------------------------------------------------------- #ifdef MBED_CLOUD_CLIENT_USER_CONFIG_FILE #include MBED_CLOUD_CLIENT_USER_CONFIG_FILE #endif #include "pal.h" #include "esfs.h" #include "esfs_file_name.h" #include "mbed-trace/mbed_trace.h" #include <string.h> // For memcmp and strncat // --------------------------------------------------------- Definitions ---------------------------------------------------------- #define TRACE_GROUP "esfs" // Maximum 4 characters // We do not really know what other uses (if any) the file system card will have. // We will assume that it may contain other files and we will keep all ESFS files in one directory. // A future enhancement could be to put files that are not to be removed at factory reset in a separate directory. #if !defined(ESFS_WORKING_DIRECTORY) #define ESFS_WORKING_DIRECTORY "WORKING" #endif #if !defined(ESFS_BACKUP_DIRECTORY) #define ESFS_BACKUP_DIRECTORY "BACKUP" #endif #define FACTORY_RESET_DIR "FR" #define FACTORY_RESET_FILE "fr_on" // We choose a size that does not take up too much stack, but minimizes the number of reads. #define ESFS_READ_CHUNK_SIZE_IN_BYTES (64) #define ESFS_BITS_IN_BYTE (8) #define ESFS_AES_BLOCK_SIZE_BYTES (16) #define ESFS_AES_IV_SIZE_BYTES (16) #define ESFS_AES_COUNTER_INDEX_IN_IV ESFS_AES_NONCE_SIZE_BYTES #define ESFS_AES_COUNTER_SIZE_BYTES (8) #define ESFS_AES_KEY_SIZE_BYTES (16) #define ESFS_AES_KEY_SIZE_BITS (ESFS_AES_KEY_SIZE_BYTES * ESFS_BITS_IN_BYTE) // Defines the size in bytes of buffers for AES encryption / decryption. // In case we have to encrypt / decrypt a bigger amount of bytes, we loop over the buffer // and encrypt / decrypt up to ESFS_AES_BUF_SIZE_BYTES bytes on each step #define ESFS_AES_BUF_SIZE_BYTES (256) // This should be incremented when the file format changes #define ESFS_FILE_FORMAT_VERSION (1) #define ESFS_FILE_COPY_CHUNK_SIZE (256) #define MAX_FULL_PATH_SIZE (PAL_MAX_FOLDER_DEPTH_CHAR + 1 + PAL_MAX(sizeof(ESFS_BACKUP_DIRECTORY), sizeof(ESFS_WORKING_DIRECTORY)) + PAL_MAX(sizeof(FACTORY_RESET_DIR) + sizeof(FACTORY_RESET_FILE), ESFS_QUALIFIED_FILE_NAME_LENGTH)) static bool esfs_initialize = false; // -------------------------------------------------- Functions Implementation ---------------------------------------------------- // --------------------------------------------------------------- // Helper Functions // --------------------------------------------------------------- esfs_result_e esfs_init(void) { esfs_result_e result = ESFS_SUCCESS; tr_info("esfs_init - enter"); if (!esfs_initialize) { palStatus_t pal_result = PAL_SUCCESS; esfs_file_t file_handle = {0}; char dir_path[MAX_FULL_PATH_SIZE] = { 0 }; pal_result = pal_fsGetMountPoint(PAL_FS_PARTITION_PRIMARY, PAL_MAX_FOLDER_DEPTH_CHAR + 1, dir_path); if (pal_result != PAL_SUCCESS) { tr_err("esfs_init() - pal_fsGetMountPoint() for working directory failed with pal_status = 0x%x", (unsigned int)pal_result); result = ESFS_ERROR; goto errorExit; } strncat(dir_path, "/" ESFS_WORKING_DIRECTORY, sizeof(ESFS_WORKING_DIRECTORY)); //Looping on first file system operation to work around IOTMORF-914 - sd-driver initialization for(int i=0 ; i<100; i++) { // Create the esfs subfolder working pal_result = pal_fsMkDir(dir_path); if ((pal_result == PAL_SUCCESS) || (pal_result == PAL_ERR_FS_NAME_ALREADY_EXIST)) break; tr_err("esfs_init() %d", i); pal_osDelay(50); } if ((pal_result != PAL_SUCCESS) && (pal_result != PAL_ERR_FS_NAME_ALREADY_EXIST)) { tr_err("esfs_init() - pal_fsMkDir() for working directory failed with pal_status = 0x%x", (unsigned int)pal_result); result = ESFS_ERROR; goto errorExit; } pal_result = pal_fsGetMountPoint(PAL_FS_PARTITION_SECONDARY, PAL_MAX_FOLDER_DEPTH_CHAR + 1, dir_path); if (pal_result != PAL_SUCCESS) { tr_err("esfs_init() - pal_fsGetMountPoint() for backup directory failed with pal_status = 0x%x", (unsigned int)pal_result); result = ESFS_ERROR; goto errorExit; } strncat(dir_path, "/" ESFS_BACKUP_DIRECTORY, sizeof(ESFS_BACKUP_DIRECTORY)); // Create the directory ESFS_BACKUP_DIRECTORY pal_result = pal_fsMkDir(dir_path); if (pal_result != PAL_SUCCESS) { // Any error apart from file exist returns error. if (pal_result != PAL_ERR_FS_NAME_ALREADY_EXIST) { tr_err("esfs_init() - pal_fsMkDir() for backup directory failed with pal_status = 0x%x", (unsigned int)pal_result); result = ESFS_ERROR; goto errorExit; } } // create the correct path for factory reset file fr_on strncat(dir_path, "/" FACTORY_RESET_DIR "/" FACTORY_RESET_FILE, sizeof(FACTORY_RESET_DIR) + sizeof(FACTORY_RESET_FILE)); pal_result = pal_fsFopen(dir_path, PAL_FS_FLAG_READONLY, &(file_handle.file)); // (res == PAL_SUCCESS) : flag file can be opened for reading --> file \FR\fr_on found // previous factory reset failed during execution // (res == PAL_ERR_FS_NO_FILE) : flag file was not found --> good scenario // (res != PAL_ERR_FS_NO_FILE) : file system problem if (pal_result == PAL_SUCCESS) { // Close the file before factory reset pal_result = pal_fsFclose(&(file_handle.file)); if (pal_result != PAL_SUCCESS) { tr_err("esfs_init() - unexpected filesystem behavior pal_fsFclose() failed with pal_status = 0x%x", (unsigned int)pal_result); result = ESFS_ERROR; goto errorExit; } // previous factory reset failed during execution - therefore we call this factory_reset again result = esfs_factory_reset(); if (result != ESFS_SUCCESS) { tr_err("esfs_init() - esfs_factory_reset() failed with esfs_result_e = 0x%x", result); result = ESFS_ERROR; goto errorExit; } } else if (pal_result != PAL_ERR_FS_NO_FILE) { tr_err("esfs_init() - unexpected filesystem behavior pal_fsFopen() failed with pal_status = 0x%x", (unsigned int)pal_result); result = ESFS_ERROR; goto errorExit; } esfs_initialize = true; } return ESFS_SUCCESS; errorExit: return result; } esfs_result_e esfs_finalize(void) { esfs_initialize = false; tr_info("esfs_finalize - enter"); return ESFS_SUCCESS; } // Validate that a file handle has been initialized by create or open. // Parameters : file_handle - [IN] A pointer to a file handle for which we calculate the size. // Return : ESFS_SUCCESS on success. Error code otherwise static esfs_result_e esfs_validate(esfs_file_t *file_handle) { if(file_handle && file_handle->blob_name_length > 0) { return ESFS_SUCCESS; } else { return ESFS_ERROR; } } //Function : esfs_not_encrypted_file_header_size // //Description: This function returns the size in bytes of the file header without the metadata values part. // This is actually the non-encrypted part of the file header. // It is useful for calculation the file pointer position for AES encryption / decryption which starts only from the // encrypted part of the file. // //Parameters : file_handle - [IN] A pointer to a file handle for which we calculate the size. // //Return : The size in bytes of the non-encrypted part of the file header static size_t esfs_not_encrypted_file_header_size(esfs_file_t *file_handle) { esfs_tlv_properties_t *tlv_properties = &(file_handle->tlv_properties); return ( file_handle->blob_name_length + // Name length field sizeof(file_handle->blob_name_length) + // Name field sizeof(uint16_t) + // Version field sizeof(uint16_t) + // Mode field (((file_handle->esfs_mode & ESFS_ENCRYPTED) != 0) ? ESFS_AES_NONCE_SIZE_BYTES : 0) + // Nonce field [non mandatory field] sizeof(tlv_properties->number_of_items) + // Metadata number of elements field (tlv_properties->number_of_items * sizeof(tlv_properties->tlv_items[0])) // Metadata tlv headers ); } // Returns the size in bytes of the file header. // This can only be called after the header has been read. // Parameters : // file_handle - [IN] A pointer to a file handle for which we calculate the size. static size_t esfs_file_header_size(esfs_file_t *file_handle) { size_t metadata_size = 0; esfs_tlv_properties_t *tlv_properties = &file_handle->tlv_properties; for(int i = 0; i < tlv_properties->number_of_items; i++) { metadata_size += tlv_properties->tlv_items[i].length_in_bytes; } return esfs_not_encrypted_file_header_size(file_handle) + metadata_size; } // Helper function to calculate the cmac on data that is written. // Parameters : // pbuf - [IN] A pointer to a buffer // num_bytes [IN] number of bytes that we request to write. // file_handle - [IN] A pointer to a file handle for which we calculate the size. // Return : ESFS_SUCCESS on success. Error code otherwise static esfs_result_e esfs_fwrite_and_calc_cmac(const void *pbuf, size_t num_bytes, esfs_file_t *file_handle) { palStatus_t res = pal_CMACUpdate(file_handle->signature_ctx, pbuf, num_bytes); if(res != PAL_SUCCESS) { tr_err("esfs_fwrite_and_calc_cmac() - pal_CMACUpdate failed with result = 0x%x", (unsigned int)res); return ESFS_ERROR; } size_t num_bytes_written; res = pal_fsFwrite(&file_handle->file, pbuf, num_bytes, &num_bytes_written); if(res != PAL_SUCCESS || num_bytes != num_bytes_written) { tr_err("esfs_fwrite_and_calc_cmac() - pal_fsFwrite failed, status = 0x%x, written bytes = %zu, expected = %zu", (unsigned int)res, num_bytes_written, num_bytes); return ESFS_ERROR; } return ESFS_SUCCESS; } // Helper function to start a cmac run. // Moves the file position to the start of the file. // Parameters : // file_handle - [IN] A pointer to a file handle. // If successful it creates a cmac context which must be destroyed with a call to esfs_cmac_finish // Return : ESFS_SUCCESS on success. Error code otherwise. static esfs_result_e esfs_cmac_start(esfs_file_t *file_handle) { unsigned char key[ESFS_CMAC_SIZE_IN_BYTES]; // Get CMAC key from PAL palStatus_t res = pal_osGetDeviceKey(palOsStorageSignatureKey128Bit, &key[0], ESFS_CMAC_SIZE_IN_BYTES); if(res != PAL_SUCCESS) { tr_err("esfs_start_cmac() - pal_osGetDeviceKey() failed with pal_status = 0x%x", (unsigned int)res); return ESFS_ERROR; } // Start CMAC with the key. Initializes signature_ctx res = pal_CMACStart(&file_handle->signature_ctx, &key[0], 128, PAL_CIPHER_ID_AES); if(res != PAL_SUCCESS) { tr_err("esfs_start_cmac() - pal_CMACStart() failed with pal_status = 0x%x", (unsigned int)res); return ESFS_ERROR; } // Seek to the start of the file res = pal_fsFseek(&file_handle->file, 0, PAL_FS_OFFSET_SEEKSET); if(res != PAL_SUCCESS) { tr_err("esfs_start_cmac() - pal_fsFseek() failed with pal status 0x%x", (unsigned int)res); // Clean up the cmac context size_t num_bytes; // Ignore error. Sets file_handle->signature_ctx to 0 on success (void)pal_CMACFinish(&file_handle->signature_ctx, &key[0], &num_bytes); return ESFS_ERROR; } return ESFS_SUCCESS; } // Helper function to read and calculate the cmac on data that is read. // The function will not update the cmac if there is no CMAC context in the file handle. // Updates the file position. // Parameters : // file_handle - [IN] A pointer to a file handle for which we calculate the cmac. // pbuf [OUT] A pointer to a buffer containing the data that is read. // num_bytes [IN] number of bytes that we request to read. // num_bytes_read[OUT] A pointer to a location in which will be written the number of bytes actually read. // Return : ESFS_SUCCESS on success. Error code otherwise static esfs_result_e esfs_cmac_read(esfs_file_t *file_handle, void *pbuf, size_t num_bytes, size_t *num_bytes_read) { palStatus_t res = pal_fsFread(&file_handle->file, pbuf, num_bytes, num_bytes_read); if(res != PAL_SUCCESS) { tr_err("esfs_cmac_read() - pal_fsFread failed with status = 0x%x", (unsigned int)res); return ESFS_ERROR; } // Update the CMAC only if there is a context. (It is allowed to read the file without calculating CMAC if // it does not need to checked.) if(file_handle->signature_ctx) { res = pal_CMACUpdate(file_handle->signature_ctx, pbuf, *num_bytes_read); if(res != PAL_SUCCESS) { tr_err("esfs_cmac_read() - pal_CMACUpdate failed with status = 0x%x", (unsigned int)res); return ESFS_ERROR; } } return ESFS_SUCCESS; } // Helper function to skip past a part of the file while calculating the cmac on the skipped data. // Updates the file position. // Parameters : // file_handle - [IN] A pointer to a file handle. // to [IN] The absolute position from the start of the file to which skip. // It must be greater than the current position and no longer that the file size. // Return : ESFS_SUCCESS on success. Error code otherwise. static esfs_result_e esfs_cmac_skip_to(esfs_file_t *file_handle, int32_t to) { // Get current position int32_t current_pos; palStatus_t res = pal_fsFtell(&file_handle->file, ¤t_pos); if (res != PAL_SUCCESS) { tr_err("esfs_cmac_skip_to() - pal_fsFtell() failed with pal_status = 0x%x", (unsigned int)res); return ESFS_ERROR; } // Iterate over the rest of file in chunks to calculate the cmac // buffer will contain only data read form the file for (int32_t i = to - current_pos; i > 0; i -= ESFS_READ_CHUNK_SIZE_IN_BYTES) { // Read a chunk // Here we read the file as is - plain text or encrypted uint8_t buffer[ESFS_READ_CHUNK_SIZE_IN_BYTES]; size_t num_bytes; esfs_result_e res = esfs_cmac_read(file_handle, buffer, PAL_MIN(i, ESFS_READ_CHUNK_SIZE_IN_BYTES), &num_bytes); if (res != ESFS_SUCCESS || num_bytes == 0) { tr_err("esfs_cmac_skip_to() failed num_bytes bytes = %zu", num_bytes); return ESFS_ERROR; } } return ESFS_SUCCESS; } // Helper function to terminate a cmac run and return the resulting cmac. // Parameters : // file_handle - [IN] A pointer to a file handle for which we calculate the cmac. // pcmac [OUT] A pointer to a buffer into which the cmac will be written. It must be at least ESFS_CMAC_SIZE_IN_BYTES. // Return : ESFS_SUCCESS on success. Error code otherwise static esfs_result_e esfs_cmac_finish(esfs_file_t *file_handle, unsigned char *pcmac) { size_t num_bytes; // Sets file_handle->signature_ctx to 0 on success palStatus_t res = pal_CMACFinish(&file_handle->signature_ctx, pcmac, &num_bytes); if(res != PAL_SUCCESS) { tr_err("esfs_finish_cmac() - pal_CMACFinish() failed with pal_status = 0x%x", (unsigned int)res); return ESFS_ERROR; } return ESFS_SUCCESS; } // Helper function to compare the passed cmac against the one in the file and to the one in the file descriptor // and then return the file position in the passed value. // Updates the file position. // Parameters : // file_handle - [IN] A pointer to a file handle for which we check the cmac. // pcmac [IN] A pointer to a buffer containing the cmac that will be compared. It must be at least ESFS_CMAC_SIZE_IN_BYTES. // position [IN] The absolute position from the start of the file to which we restore the file position. // Return : ESFS_SUCCESS on success. Error code otherwise. static esfs_result_e esfs_cmac_check_and_restore(esfs_file_t *file_handle, unsigned char *pcmac, int32_t position) { // Read the signature from the file unsigned char file_cmac[ESFS_CMAC_SIZE_IN_BYTES]; size_t num_bytes; palStatus_t res = pal_fsFread(&file_handle->file, &file_cmac[0], ESFS_CMAC_SIZE_IN_BYTES, &num_bytes); if (res != PAL_SUCCESS || num_bytes != ESFS_CMAC_SIZE_IN_BYTES) { tr_err("esfs_cmac_check_and_restore() - pal_fsFread() failed with pal result = 0x%x and num_bytes bytes = %zu", (unsigned int)res, num_bytes); return ESFS_ERROR; } // Compare the cmac that we read from the file with the one that is passed and check it against // the one recorded in esfs_open in order to verify that the file is the same as the one that was opened. if(memcmp(&file_cmac[0], pcmac, ESFS_CMAC_SIZE_IN_BYTES) != 0 || memcmp(&file_cmac[0], &file_handle->cmac[0], ESFS_CMAC_SIZE_IN_BYTES) != 0) { tr_err("esfs_cmac_check_and_restore() - cmac that we read from the file does not match the one that we calculated"); return ESFS_CMAC_DOES_NOT_MATCH; } // Set the file position to the byte indicated by position. res = pal_fsFseek(&file_handle->file, position, PAL_FS_OFFSET_SEEKSET); if(res != PAL_SUCCESS) { tr_err("esfs_cmac_check_and_restore() - pal_fsFseek() failed with pal status 0x%x", (unsigned int)res); return ESFS_ERROR; } return ESFS_SUCCESS; } //Function : esfs_memcpy_reverse // //Description: This function copies the first <len_bytes> bytes from input buffer <src_ptr> to output buffer <dest_ptr> in // reversed order (e.g. '1' '2' '3' data array will be copied as '3' '2' '1'). // Note: The function assumes that the memory areas of the input buffers src_ptr and dest_ptr do not overlap. // //Parameters : dest_ptr - [IN / OUT] A pointer to the destination buffer to which bytes will be copied. // src_ptr - [IN] A pointer to the source buffer from which bytes will be copied. // len_bytes - [IN] Number of bytes to be copied. // //Return : A pointer to the output buffer <dest_ptr> static void *esfs_memcpy_reverse(void *dest_ptr, const void *src_ptr, uint32_t len_bytes) { uint8_t *tmp_dest_ptr = (uint8_t *)dest_ptr; const uint8_t *tmp_src_ptr = (const uint8_t *)src_ptr; // Make the reverse copy while(len_bytes > 0) { *(tmp_dest_ptr++) = *(tmp_src_ptr + len_bytes - 1); len_bytes--; } return dest_ptr; } //Function : esfs_calc_file_pos_for_aes // //Description: This function calculates the file position for the purpose of AES encrypt / decrypt: // The returned position is relative to the beginning of the encrypted data. // The file is encrypted starting from the meta data part (the meta data values). // //Parameters : file_handle - [IN] A pointer to a file handle on which we calculate the position. // position - [OUT] A pointer to size_t to be filled in with the returned position. // //Return : ESFS_SUCCESS on success. Error code otherwise static esfs_result_e esfs_calc_file_pos_for_aes(esfs_file_t *file_handle, size_t *position) { palStatus_t pal_status = PAL_SUCCESS; size_t non_encrypt_size = 0; *position = 0; // Get current position inside the file pal_status = pal_fsFtell( &(file_handle->file), (int32_t *)position ); if(pal_status != PAL_SUCCESS) { tr_err("esfs_calc_file_pos_for_aes() - pal_fsFtell() failed with pal_status = 0x%x", (unsigned int)pal_status); return ESFS_ERROR; } // Calculate non_encrypt_size to be subtracted from position non_encrypt_size = esfs_not_encrypted_file_header_size(file_handle); if(*position < non_encrypt_size) { tr_err("esfs_calc_file_pos_for_aes() - Error. Position is in non encrypted part."); return ESFS_ERROR; } *position -= non_encrypt_size; return ESFS_SUCCESS; } //Function : esfs_set_counter_in_iv_by_file_pos // //Description: This function fills in the last 8 bytes of the IV [iv128_arr] with the counter calculated according to // the input position. // //Parameters : position - [IN] The position in the file when count starts from the encrypted data part (the meta data values). // iv128_arr - [IN/OUT] A 16 bytes buffer holding the IV. // First 8 bytes contain the NONCE, and last 8 bytes will be filled in with the counter. // //Return : ESFS_SUCCESS on success. Error code otherwise static void esfs_set_counter_in_iv_by_file_pos(size_t position, uint8_t *iv128_arr) { uint64_t counter = 0; // Calculate counter part of IV counter = (uint64_t)(position / ESFS_AES_BLOCK_SIZE_BYTES); // Copy the counter part to the IV #if BIG__ENDIAN == 1 memcpy(iv128_arr + ESFS_AES_COUNTER_INDEX_IN_IV, &counter, ESFS_AES_COUNTER_SIZE_BYTES); #else esfs_memcpy_reverse(iv128_arr + ESFS_AES_COUNTER_INDEX_IN_IV, &counter, ESFS_AES_COUNTER_SIZE_BYTES); #endif } //Function : esfs_aes_enc_dec_by_file_pos // //Description: This function encrypts / decrypts data using AES-CTR. // This is the basic function used for AES encrypt / decrypt. // Due to the nature of AES-CTR which works on blocks, special handling is required in case the data in the file is not // on block boundaries. In this case we encrypt / decrypt this "partial block data" in a temporal buffer after copying // the data to the corresponding index inside this buffer. The rest of the data is being encrypted / decrypted normally. // //Parameters : aes_ctx - [IN] The per-initiated AES context. // buf_in - [IN] A buffer containing to data to be encrypted / decrypted. // buf_out - [OUT] A buffer to be filled in with the encrypted / decrypted data. // len_bytes - [IN] Number of bytes to encrypt / decrypt. // position - [IN] The position in the file when count starts from the encrypted data part (the meta data values). // nonce64_ptr - [IN] An 8 bytes buffer holding the NONCE part of the IV. // //Return : ESFS_SUCCESS on success. Error code otherwise static esfs_result_e esfs_aes_enc_dec_by_file_pos( palAesHandle_t aes_ctx, const uint8_t *buf_in, uint8_t *buf_out, size_t len_bytes, size_t position, uint8_t *nonce64_ptr ) { palStatus_t pal_status = PAL_SUCCESS; uint8_t prev_remainder = 0; // Size in bytes of partial block PREVIOUSLY encrypted / decrypted uint8_t partial_block_size = 0; // Size in bytes of partial block for NEXT encrypt / decrypt uint8_t partial_block_size_temp = 0; uint8_t partial_block_in[ESFS_AES_BLOCK_SIZE_BYTES] = {0}; // Will contain data for next partial encrypt / decrypt uint8_t partial_block_out[ESFS_AES_BLOCK_SIZE_BYTES] = {0}; uint8_t iv_arr[ESFS_AES_IV_SIZE_BYTES] = {0}; // Will contain nonce [bytes 0 - 7] and counter [bytes 8 - 15] // -------- partial_block_in: Size = block_size [16 bytes] // | // | // \|/ // // ----------------------------------------------------------------------------------------- // | | | | // | 0 ... 0 | Data copied form buf_in | 0 ... 0 | // | | | | // ----------------------------------------------------------------------------------------- // ^ ^ ^ // | | | // | | | // | | | // Size: prev_remainder | Size: might be 0 // | // | // Size: partial_block_size // (might consume the buffer till its end) prev_remainder = (position % ESFS_AES_BLOCK_SIZE_BYTES); partial_block_size_temp = ESFS_AES_BLOCK_SIZE_BYTES - prev_remainder; partial_block_size = PAL_MIN(partial_block_size_temp, len_bytes); // Prepare partial_block_in: Copy data for next encrypt / decrypt from buf_in to partial_block_in memcpy(partial_block_in + prev_remainder, buf_in, partial_block_size); // Prepare iv_arr: Copy nonce into bytes [0 - 7] of IV buffer memcpy(iv_arr, nonce64_ptr, ESFS_AES_NONCE_SIZE_BYTES); // Prepare iv_arr: Set counter in bytes [8 - 15] of IV buffer esfs_set_counter_in_iv_by_file_pos(position, iv_arr); // Encrypt / decrypt partial block [run on entire block, and copy later only desired part) pal_status = pal_aesCTRWithZeroOffset(aes_ctx, partial_block_in, partial_block_out, ESFS_AES_BLOCK_SIZE_BYTES, iv_arr); if(pal_status != PAL_SUCCESS) { tr_err("esfs_aes_enc_dec_by_file_pos() - pal_aesCTRWithZeroOffset() failed with pal_status = 0x%x", (unsigned int)pal_status); return ESFS_ERROR; } // Copy partial_block_out to buf_out memcpy(buf_out, partial_block_out + prev_remainder, partial_block_size); // Encrypt / decrypt the rest of the data if(len_bytes > partial_block_size) { // Set updated counter in bytes [8 - 15] of IV buffer esfs_set_counter_in_iv_by_file_pos(position + partial_block_size, iv_arr); pal_status = pal_aesCTRWithZeroOffset(aes_ctx, buf_in + partial_block_size, buf_out + partial_block_size, len_bytes - partial_block_size, iv_arr); if(pal_status != PAL_SUCCESS) { tr_err("esfs_aes_enc_dec_by_file_pos() - pal_aesCTRWithZeroOffset() failed with pal_status = 0x%x", (unsigned int)pal_status); return ESFS_ERROR; } } return ESFS_SUCCESS; } //Function : esfs_read_and_decrypt // //Description: This function reads encrypted data from a file, decrypts it, and writes it into a buffer. // //Parameters : file_handle - [IN] A pointer to a file handle from which we read data. // buffer - [IN] The buffer to fill in with decrypted file data. // bytes_to_read - [IN] Number of bytes to read from the file. // read_bytes_ptr - [OUT] A pointer to size_t to be filled in with number of bytes actually read from the file. // //Return : ESFS_SUCCESS on success. Error code otherwise static esfs_result_e esfs_read_and_decrypt(esfs_file_t *file_handle, void *buffer, size_t bytes_to_read, size_t *read_bytes_ptr) { esfs_result_e result = ESFS_SUCCESS; size_t position = 0; // Get file pointer position for AES - Must be done before calling pal_fsFread() which modifies the file pointer position result = esfs_calc_file_pos_for_aes(file_handle, &position); if(result != ESFS_SUCCESS) { tr_err("esfs_read_and_decrypt() - esfs_calc_file_pos_for_aes() failed with status = 0x%x", result); return result; } // Read file's encrypted data into buffer result = esfs_cmac_read(file_handle, buffer, bytes_to_read, read_bytes_ptr ); if((result != ESFS_SUCCESS) || (*read_bytes_ptr != bytes_to_read)) { tr_err("esfs_read_and_decrypt() - esfs_cmac_read() failed with ESFS_status = 0x%x", (unsigned int)result); return ESFS_ERROR; } // AES decrypt in-place - decrypt the encrypted data inside buffer, into buffer [out parameter] result = esfs_aes_enc_dec_by_file_pos(file_handle->aes_ctx, buffer, buffer, bytes_to_read, position, file_handle->nonce); if(result != ESFS_SUCCESS) { tr_err("esfs_read_and_decrypt() - esfs_aes_enc_dec_by_file_pos() failed with status = 0x%x", (unsigned int)result); return result; } return ESFS_SUCCESS; } //Function : esfs_encrypt_fwrite_and_calc_cmac // //Description: This function takes a plain text buffer, encrypts it, writes the encrypted data to a file, and updates the // CMAC signature. // // Since we cannot modify the data of the input buffer (const), this operation cannot be done in-place, so we need // to use another buffer for the encryption result. In order to avoid dynamically allocation, we use a buffer // of size ESFS_AES_BUF_SIZE_BYTES statically allocated on the stack. This forces us to encrypt and write in a loop - // each iteration encrypts and writes maximum size of ESFS_AES_BUF_SIZE_BYTES bytes. // //Parameters : buffer - [IN] The buffer to encrypt and write to the file. // bytes_to_write - [IN] The number of bytes to write. // file_handle - [IN] A pointer to a file handle to which we write the data. // //Return : ESFS_SUCCESS on success. Error code otherwise static esfs_result_e esfs_encrypt_fwrite_and_calc_cmac(const void *buffer, size_t bytes_to_write, esfs_file_t *file_handle) { esfs_result_e result = ESFS_SUCCESS; size_t position = 0; size_t remaining_bytes_to_write = bytes_to_write; const uint8_t *buffer_tmp_ptr = (uint8_t *)buffer; // Will point to the next reading point in buffer as we read it uint8_t encrypted_data[ESFS_AES_BUF_SIZE_BYTES] = {0}; // Will hold encrypted data to be written to the file if(buffer == NULL) { tr_err("esfs_encrypt_fwrite_and_calc_cmac() - Bad arguments error. Input buffer is NULL."); return ESFS_ERROR; } // Get file pointer position for AES - Must be done before calling esfs_fwrite_and_calc_cmac() which modifies the file pointer position result = esfs_calc_file_pos_for_aes(file_handle, &position); if(result != ESFS_SUCCESS) { tr_err("esfs_encrypt_fwrite_and_calc_cmac() - esfs_calc_file_pos_for_aes failed with result=0x%x", result); return result; } // On every iteration in the loop, encrypt ESFS_AES_BUF_SIZE_BYTES bytes, and write them to the file while(remaining_bytes_to_write >= ESFS_AES_BUF_SIZE_BYTES) { // AES encrypt into encrypted_data result = esfs_aes_enc_dec_by_file_pos(file_handle->aes_ctx, buffer_tmp_ptr, encrypted_data, ESFS_AES_BUF_SIZE_BYTES, position, file_handle->nonce); if(result != ESFS_SUCCESS) { tr_err("esfs_encrypt_fwrite_and_calc_cmac() - esfs_aes_enc_dec_by_file_pos failed with result=0x%x", result); return result; } // Write the encrypted data to the file result = esfs_fwrite_and_calc_cmac(encrypted_data, ESFS_AES_BUF_SIZE_BYTES, file_handle); if((result != ESFS_SUCCESS)) { tr_err("esfs_encrypt_fwrite_and_calc_cmac() - esfs_fwrite_and_calc_cmac() status = 0x%x", (unsigned int)result); // esfs_fwrite_and_calc_cmac() failed so we cannot be sure of the state of the file - mark the file as invalid file_handle->file_invalid = 1; return ESFS_ERROR; } position += ESFS_AES_BUF_SIZE_BYTES; buffer_tmp_ptr += ESFS_AES_BUF_SIZE_BYTES; remaining_bytes_to_write -= ESFS_AES_BUF_SIZE_BYTES; } // AES encrypt the leftover of buffer if(remaining_bytes_to_write > 0) { // AES encrypt into encrypted_data result = esfs_aes_enc_dec_by_file_pos(file_handle->aes_ctx, buffer_tmp_ptr, encrypted_data, remaining_bytes_to_write, position, file_handle->nonce); if(result != ESFS_SUCCESS) { tr_err("esfs_encrypt_fwrite_and_calc_cmac() - esfs_aes_enc_dec_by_file_pos failed with result=0x%x", result); return result; } // Write the encrypted data to the file result = esfs_fwrite_and_calc_cmac(encrypted_data, remaining_bytes_to_write, file_handle); if((result != ESFS_SUCCESS)) { tr_err("esfs_encrypt_fwrite_and_calc_cmac() - esfs_fwrite_and_calc_cmac() status = 0x%x", (unsigned int)result); // esfs_fwrite_and_calc_cmac() failed so we cannot be sure of the state of the file - mark the file as invalid file_handle->file_invalid = 1; return ESFS_ERROR; } } return ESFS_SUCCESS; } esfs_result_e esfs_reset(void) { esfs_result_e result = ESFS_SUCCESS; palStatus_t pal_result = PAL_SUCCESS; char dir_path[MAX_FULL_PATH_SIZE] = { 0 }; tr_info("esfs_reset - enter"); pal_result = pal_fsGetMountPoint(PAL_FS_PARTITION_PRIMARY, PAL_MAX_FOLDER_DEPTH_CHAR + 1, dir_path); if (pal_result != PAL_SUCCESS) { tr_err("esfs_reset() - pal_fsGetMountPoint() for working directory failed with pal_status = 0x%x", (unsigned int)pal_result); result = ESFS_ERROR; goto errorExit; } strncat(dir_path, "/" ESFS_WORKING_DIRECTORY, sizeof(ESFS_WORKING_DIRECTORY)); // delete the files in working dir pal_result = pal_fsRmFiles(dir_path); // the use case is that esfs folder may not exist if ((pal_result != PAL_SUCCESS) && (pal_result != PAL_ERR_FS_NO_FILE) && (pal_result != PAL_ERR_FS_NO_PATH)) { tr_err("esfs_reset() - pal_fsRmFiles(ESFS_WORKING_DIRECTORY) failed with pal_status = 0x%x", (unsigned int)pal_result); result = ESFS_ERROR; goto errorExit; } // delete working directory pal_result = pal_fsRmDir(dir_path); if (pal_result != PAL_SUCCESS) { // Any error apart from dir not exist returns error. if ((pal_result != PAL_ERR_FS_NO_FILE) && (pal_result != PAL_ERR_FS_NO_PATH)) { tr_err("esfs_reset() - pal_fsRmDir(ESFS_WORKING_DIRECTORY) failed with pal_status = 0x%x", (unsigned int)pal_result); result = ESFS_ERROR; goto errorExit; } } pal_result = pal_fsGetMountPoint(PAL_FS_PARTITION_SECONDARY, PAL_MAX_FOLDER_DEPTH_CHAR + 1, dir_path); if (pal_result != PAL_SUCCESS) { tr_err("esfs_reset() - pal_fsGetMountPoint() for backup directory failed with pal_status = 0x%x", (unsigned int)pal_result); result = ESFS_ERROR; goto errorExit; } strncat(dir_path, "/" ESFS_BACKUP_DIRECTORY, sizeof(ESFS_BACKUP_DIRECTORY)); // delete the files in backup dir pal_result = pal_fsRmFiles(dir_path); // the use case is that esfs folder may not exist if ((pal_result != PAL_SUCCESS) && (pal_result != PAL_ERR_FS_NO_FILE) && (pal_result != PAL_ERR_FS_NO_PATH)) { tr_err("esfs_reset() - pal_fsRmFiles(ESFS_BACKUP_DIRECTORY) failed with pal_status = 0x%x", (unsigned int)pal_result); result = ESFS_ERROR; goto errorExit; } pal_result = pal_fsRmDir(dir_path); if (pal_result != PAL_SUCCESS) { // Any error apart from dir not exist returns error. if ((pal_result != PAL_ERR_FS_NO_FILE) && (pal_result != PAL_ERR_FS_NO_PATH)) { tr_err("esfs_reset() - pal_fsRmDir(ESFS_BACKUP_DIRECTORY) failed with pal_status = 0x%x", (unsigned int)pal_result); result = ESFS_ERROR; goto errorExit; } } if (esfs_finalize() != ESFS_SUCCESS) { tr_err("esfs_reset() - esfs_finalize() failed"); result = ESFS_ERROR; goto errorExit; } if (esfs_init() != ESFS_SUCCESS) { tr_err("esfs_reset() - esfs_init() failed"); result = ESFS_ERROR; goto errorExit; } return ESFS_SUCCESS; errorExit: return result; } esfs_result_e esfs_factory_reset(void) { palStatus_t pal_result = PAL_SUCCESS; esfs_result_e result = ESFS_SUCCESS; esfs_file_t file_handle = { 0 }; char working_dir_path[MAX_FULL_PATH_SIZE] = { 0 }; char full_path_backup_dir[MAX_FULL_PATH_SIZE] = { 0 }; bool is_single_partition = true; tr_info("esfs_factory_reset - enter"); pal_result = pal_fsGetMountPoint(PAL_FS_PARTITION_SECONDARY, PAL_MAX_FOLDER_DEPTH_CHAR + 1, full_path_backup_dir); if (pal_result != PAL_SUCCESS) { tr_err("esfs_factory_reset() - pal_fsGetMountPoint() for backup directory failed with pal_status = 0x%x", (unsigned int)pal_result); return ESFS_ERROR; } strncat(full_path_backup_dir, "/" ESFS_BACKUP_DIRECTORY "/" FACTORY_RESET_DIR, sizeof(ESFS_BACKUP_DIRECTORY) + sizeof(FACTORY_RESET_DIR)); // Create the factory reset subfolder - FR pal_result = pal_fsMkDir(full_path_backup_dir); if (pal_result != PAL_SUCCESS) { // Any error apart from file exist returns error. if (pal_result != PAL_ERR_FS_NAME_ALREADY_EXIST) { tr_err("esfs_factory_reset() - pal_fsMkDir(ESFS_BACKUP_DIRECTORY/FACTORY_RESET_DIR) failed with pal_status = 0x%x", (unsigned int)pal_result); result = ESFS_ERROR; goto errorExit; } } strncat(full_path_backup_dir, "/" FACTORY_RESET_FILE, sizeof(FACTORY_RESET_FILE)); // Create the fr_on flag file pal_result = pal_fsFopen(full_path_backup_dir, PAL_FS_FLAG_READWRITEEXCLUSIVE, &(file_handle.file)); // (res == PAL_SUCCESS) : factory reset is called on the first time // (res == PAL_ERR_FS_NAME_ALREADY_EXIST) : factory reset is called again after it was failed // on the first time and therefore the file exists if ((pal_result != PAL_SUCCESS) && (pal_result != PAL_ERR_FS_NAME_ALREADY_EXIST)) { tr_err("esfs_factory_reset() - unexpected filesystem behavior pal_fsFopen() failed with pal_status = 0x%x", (unsigned int)pal_result); result = ESFS_ERROR; goto errorExit; } // close the file only if we opened it if (pal_result == PAL_SUCCESS) { pal_result = pal_fsFclose(&(file_handle.file)); if (pal_result != PAL_SUCCESS) { tr_err("esfs_factory_reset() - unexpected filesystem behavior pal_fsFclose() failed with pal_status = 0x%x", (unsigned int)pal_result); result = ESFS_ERROR; goto errorExit; } } pal_result = pal_fsGetMountPoint(PAL_FS_PARTITION_PRIMARY, PAL_MAX_FOLDER_DEPTH_CHAR + 1, working_dir_path); if (pal_result != PAL_SUCCESS) { tr_err("esfs_factory_reset() - pal_fsGetMountPoint() for working directory failed with pal_status = 0x%x", (unsigned int)pal_result); result = ESFS_ERROR; goto errorExit; } // Check if there is a single partition by comparing the primary and secondary mount points. // This is the only reliable way to do it, since the logic that determines the number of partitions is // hidden behind the PAL API. pal_result = pal_fsGetMountPoint(PAL_FS_PARTITION_SECONDARY, PAL_MAX_FOLDER_DEPTH_CHAR + 1, full_path_backup_dir); if (pal_result != PAL_SUCCESS) { tr_err("esfs_factory_reset() - pal_fsGetMountPoint() for backup directory failed with pal_status = 0x%x", (unsigned int)pal_result); result = ESFS_ERROR; goto errorExit; } is_single_partition = (strcmp(working_dir_path,full_path_backup_dir) == 0); strncat(working_dir_path, "/" ESFS_WORKING_DIRECTORY, sizeof(ESFS_WORKING_DIRECTORY)); // We can only format the working folder if it is dedicated for exclusive use of esfs and // it is not the only partition that exists. The assumption here is that if it is the only partition, // then the backup folder is also on that partition. In that case, formatting would remove the backup partition, // which we do not want to do! if (pal_fsIsPrivatePartition(PAL_FS_PARTITION_PRIMARY) && !is_single_partition) { pal_result = pal_fsFormat(PAL_FS_PARTITION_PRIMARY); if (pal_result != PAL_SUCCESS) { tr_err("esfs_factory_reset() - pal_fsFormat() for working directory failed with pal_status = 0x%x", (unsigned int)pal_result); result = ESFS_ERROR; goto errorExit; } pal_result = pal_fsMkDir(working_dir_path); if (pal_result != PAL_SUCCESS) { tr_err("esfs_factory_reset() - pal_fsMkDir(ESFS_WORKING_DIRECTORY) failed with pal_status = 0x%x", (unsigned int)pal_result); result = ESFS_ERROR; goto errorExit; } } else { // delete the files in working dir pal_result = pal_fsRmFiles(working_dir_path); // the use case is that esfs folder may not exist if ((pal_result != PAL_SUCCESS) && (pal_result != PAL_ERR_FS_NO_FILE) && (pal_result != PAL_ERR_FS_NO_PATH)) { tr_err("esfs_factory_reset() - pal_fsRmFiles(ESFS_WORKING_DIRECTORY) failed with pal_status = 0x%x", (unsigned int)pal_result); result = ESFS_ERROR; goto errorExit; } } pal_result = pal_fsGetMountPoint(PAL_FS_PARTITION_SECONDARY, PAL_MAX_FOLDER_DEPTH_CHAR + 1, full_path_backup_dir); if (pal_result != PAL_SUCCESS) { tr_err("esfs_factory_reset() - pal_fsGetMountPoint() for backup directory failed with pal_status = 0x%x", (unsigned int)pal_result); return ESFS_ERROR; } strncat(full_path_backup_dir, "/" ESFS_BACKUP_DIRECTORY, sizeof(ESFS_BACKUP_DIRECTORY)); pal_result = pal_fsCpFolder(full_path_backup_dir, working_dir_path); if ((pal_result != PAL_SUCCESS) && (pal_result != PAL_ERR_FS_NO_FILE)) { tr_err("esfs_factory_reset() - pal_fsCpFolder() from backup to working failed with pal_status = 0x%x", (unsigned int)pal_result); result = ESFS_ERROR; goto errorExit; } strncat(full_path_backup_dir, "/" FACTORY_RESET_DIR "/" FACTORY_RESET_FILE, sizeof(FACTORY_RESET_DIR) + sizeof(FACTORY_RESET_FILE)); // delete the flag file because factory reset flow ended successfully pal_result = pal_fsUnlink(full_path_backup_dir); if (pal_result != PAL_SUCCESS) { tr_err("esfs_factory_reset() - pal_fsUnlink(ESFS_BACKUP_DIRECTORY/FACTORY_RESET_DIR/FACTORY_RESET_FILE) failed with pal_status = 0x%x", (unsigned int)pal_result); result = ESFS_ERROR; goto errorExit; } return ESFS_SUCCESS; errorExit: return result; } // Internal function to read header information and check its' validity. // Checks the name given against the name written in the file. // Checks the version. // Initializes some fields of file_handle: blob_name_length, esf_mode // Assumes that the read position is at the start of the file. // CMAC is calculated. // Parameters : // name [IN] A pointer to an array of binary data that uniquely identifies the file. // name_length [IN] size in bytes of the name. The minimum is 1 and the maximum is ESFS_MAX_NAME_LENGTH. // file_handle [IN] A pointer to a file handle on which we calculate the position. // return esf_success - name matches; // ESFS_HASH_CONFLICT - name does not match // ESFS_WRONG_FILE_VERSION - version does not match // ESFS_ERROR - other problem // On ESFS_SUCCESS or ESFS_HASH_CONFLICT the read position is set after the name. // On failure the position is undefined. static esfs_result_e esfs_check_file_validity(const uint8_t* name, size_t name_length, esfs_file_t *file_handle) { esfs_result_e result = ESFS_ERROR; // Read the version uint16_t version; size_t num_bytes; result = esfs_cmac_read(file_handle, &version , sizeof(version), &num_bytes); if (result != ESFS_SUCCESS || num_bytes != sizeof(version)) { tr_err("esfs_check_file_validity() - esfs_cmac_read() failed with ESFS result = 0x%x and num_bytes bytes = %zu", (unsigned int)result, num_bytes); goto errorExit; } // Check that the files version is the same as the source code version. if(version != ESFS_FILE_FORMAT_VERSION) { tr_err("esfs_check_file_validity() - invalid version: failed with version = %u instead of %u", (unsigned int)version, (unsigned int)ESFS_FILE_FORMAT_VERSION); result = ESFS_INVALID_FILE_VERSION; goto errorExit; } // Read the mode result = esfs_cmac_read(file_handle, (void *)( &file_handle->esfs_mode ), sizeof(file_handle->esfs_mode), &num_bytes); if (result != ESFS_SUCCESS || num_bytes != sizeof(file_handle->esfs_mode)) { tr_err("esfs_check_file_validity() mode - failed num_bytes bytes = %zu", num_bytes); goto errorExit; } // The mode is not used further in the opening process, so no further checks need be performed as cmac check will detect any // tampering. // Read the name length result = esfs_cmac_read(file_handle, (void *)( &file_handle->blob_name_length ), sizeof(file_handle->blob_name_length), &num_bytes); if (result != ESFS_SUCCESS || num_bytes != sizeof(file_handle->blob_name_length)) { tr_err("esfs_check_file_validity() name length- esfs_cmac_read() failed with result = 0x%x and num_bytes bytes = %zu", (unsigned int)result, num_bytes); goto errorExit; } // Check that the name in the file is the same length as the one given. It cannot be greater than ESFS_MAX_NAME_LENGTH // because that is checked on entry to the function. if (name_length != file_handle->blob_name_length) { tr_err("esfs_check_file_validity() - name length conflict"); // The hash of the name conflicts with the hash of another name. result = ESFS_HASH_CONFLICT; goto errorExit; } // Check the name chunk by chunk for (int i = name_length; i > 0; i -= ESFS_READ_CHUNK_SIZE_IN_BYTES) { // Read a chunk char buffer[ESFS_READ_CHUNK_SIZE_IN_BYTES]; result = esfs_cmac_read(file_handle, (void *)buffer, PAL_MIN(i, ESFS_READ_CHUNK_SIZE_IN_BYTES), &num_bytes); if (result != ESFS_SUCCESS || num_bytes == 0) { tr_err("esfs_check_file_validity() - read name failed with ESFS result = 0x%x and num_bytes bytes = %zu", (unsigned int)result, num_bytes); goto errorExit; } // Check that the chunk matches //tr_info("Comparing %s (%d bytes) name_length=%d", name, (int )num_bytes,(int )name_length); if (memcmp(buffer, name, num_bytes) != 0) { tr_err("esfs_check_file_validity() - esfs hash conflict : The hash of the name conflicts with the hash of another name"); // The hash of the name conflicts with the hash of another name. result = ESFS_HASH_CONFLICT; goto errorExit; } // Advance past what we just checked. name += num_bytes; } return ESFS_SUCCESS; errorExit: return result; } // Internal function to check the name against the name written in the file. // Assume that the read position is set to before the name length. // Parameters : // fd - [IN] A pointer to a file descriptor. // file_size [OUT] A pointer to a value into which the file size is returned. // return esf_success - name matches; // ESFS_HASH_CONFLICT - name does not match ; // ESFS_ERROR - other problem // On ESFS_SUCCESS or ESFS_HASH_CONFLICT the read position is set after the name. // On failure the position is undefined. // Helper function // Restores current position unless it fails. // On failure the position is undefined. static palStatus_t esfs_get_physical_file_size(palFileDescriptor_t* fd, int32_t *file_size) { palStatus_t res; // Get current position int32_t current_pos; res = pal_fsFtell(fd, ¤t_pos); if (res != PAL_SUCCESS) { tr_err("esfs_get_physical_file_size() - pal_fsFtell() failed with pal_status = 0x%x", (unsigned int)res); goto errorExit; } // Seek to end of file res = pal_fsFseek(fd, 0, PAL_FS_OFFSET_SEEKEND); if (res != PAL_SUCCESS) { tr_err("esfs_get_physical_file_size() - pal_fsFseek() failed with pal_status = 0x%x", (unsigned int)res); goto errorExit; } // Get new position res = pal_fsFtell(fd, file_size); if (res != PAL_SUCCESS) { tr_err("esfs_get_physical_file_size() - pal_fsFtell() failed with pal_status = 0x%x", (unsigned int)res); goto errorExit; } // Restore old position res = pal_fsFseek(fd, current_pos, PAL_FS_OFFSET_SEEKSET); if (res != PAL_SUCCESS) { tr_err("esfs_get_physical_file_size() - pal_fsFseek() failed with pal_status = 0x%x", (unsigned int)res); goto errorExit; } errorExit: return res; } // Copy one file to another. // Parameters : // src_file [IN] A pointer to a string containing the source file name. // src_file [IN] A pointer to a string containing the destination file name. // Return : ESFS_SUCCESS on success. Error code otherwise static esfs_result_e esfs_copy_file(const char *src_file, const char *dst_file) { bool is_src_file_opened = false; bool is_dst_file_opened = false; esfs_file_t file_handle = { 0 }; esfs_file_t file_handle_copy = { 0 }; esfs_result_e result = ESFS_ERROR; palStatus_t res = PAL_SUCCESS; size_t bytes_to_read = ESFS_FILE_COPY_CHUNK_SIZE; size_t num_bytes_read = 0; size_t num_bytes_write = 0; uint8_t buffer[ESFS_FILE_COPY_CHUNK_SIZE] = {0}; int32_t file_size = 0; int32_t copied_bytes = 0; // Open src file read only mode res = pal_fsFopen(src_file, PAL_FS_FLAG_READONLY, &(file_handle.file)); if (res != PAL_SUCCESS) { // File cannot be opened so return an error tr_err("esfs_copy_file() - pal_fsFopen() src file failed with pal_status = 0x%x", (unsigned int)res); result = ESFS_NOT_EXISTS; goto errorExit; } is_src_file_opened = true; // Open for reading and writing exclusively, If the file already exists, trunced file res = pal_fsFopen(dst_file, PAL_FS_FLAG_READWRITETRUNC, &(file_handle_copy.file)); if (res != PAL_SUCCESS) { // File cannot be opened so return an error tr_err("esfs_copy_file() - pal_fsFopen() dst file failed with pal_status = 0x%x", (unsigned int)res); result = ESFS_ERROR; goto errorExit; } is_dst_file_opened = true; res = esfs_get_physical_file_size(&(file_handle.file), &file_size); if (res != PAL_SUCCESS) { tr_err("esfs_copy_file() - esfs_get_physical_file_size() failed with pal_status = 0x%x", (unsigned int)res); result = ESFS_ERROR; goto errorExit; } while (copied_bytes < file_size) { if (copied_bytes + (int32_t)bytes_to_read > file_size) { bytes_to_read = file_size - copied_bytes; } res = pal_fsFread(&(file_handle.file), buffer, bytes_to_read, &num_bytes_read); if (res != PAL_SUCCESS) { tr_err("esfs_copy_file() - pal_fsFread() failed with pal_status = 0x%x", (unsigned int)res); result = ESFS_ERROR; goto errorExit; } res = pal_fsFwrite(&(file_handle_copy.file), buffer, bytes_to_read, &num_bytes_write); if ((res != PAL_SUCCESS) || (num_bytes_write != bytes_to_read)) { tr_err("esfs_copy_file() - pal_fsFwrite() failed with pal result = 0x%x and num_bytes_write bytes = %zu", (unsigned int)res, num_bytes_write); result = ESFS_ERROR; goto errorExit; } copied_bytes += bytes_to_read; } res = pal_fsFclose(&(file_handle.file)); if (res != PAL_SUCCESS) { tr_err("esfs_copy_file() - pal_fsFclose() for src file failed with pal_status = 0x%x", (unsigned int)res); result = ESFS_ERROR; goto errorExit; } res = pal_fsFclose(&(file_handle_copy.file)); if (res != PAL_SUCCESS) { tr_err("esfs_copy_file() - pal_fsFclose() for dst file failed with pal_status = 0x%x", (unsigned int)res); result = ESFS_ERROR; goto errorExit; } return ESFS_SUCCESS; errorExit: if (is_src_file_opened) { // we will not delete the src file pal_fsFclose(&(file_handle.file)); } if (is_dst_file_opened) { pal_fsFclose(&(file_handle_copy.file)); // Clean up if possible. Ignore return value. (void)pal_fsUnlink(dst_file); } return result; } // Internal function to create a new file and open it for writing. // Does not return error if file exists. // Keep all the conditions that allow the file creation in a single function, esfs_create, while the // esfs_create_internal will concentrate on file creation mechanics. // Parameters: // name [IN] A pointer to an array of binary data that uniquely identifies the file. // name_length [IN] size in bytes of the name. The minimum is 1 and the maximum is ESFS_MAX_NAME_LENGTH. // meta_data [IN] A pointer to an array of TLVs structures with meta_data_qty members // meta_data_qty[IN] number of tlvs in the array pointed by meta_data parameter. Minimum is 0 maximum is ESFS_MAX_TYPE_LENGTH_VALUES // esfs_mode [IN] a bit map combination of values from enum EsfsMode. // file_handle [IN/OUT] Pointer to the handle data structure into which to write the new handle. // returns ESFS_SUCCESS The file handle can be used in other esfs functions. It must be closed to release it. // ESFS_ERROR - other problem static esfs_result_e esfs_create_internal( const uint8_t *name, size_t name_length, const esfs_tlv_item_t *meta_data, size_t meta_data_qty, uint16_t esfs_mode, esfs_file_t *file_handle, const char* full_path_to_create ) { esfs_result_e result = ESFS_ERROR; int32_t position = 0; size_t i; uint16_t file_created = 0; uint16_t cmac_created = 0; uint16_t u16 = ESFS_FILE_FORMAT_VERSION; // Create the file. // Note that we always overwrite any previous file. palStatus_t res = pal_fsFopen(full_path_to_create, PAL_FS_FLAG_READWRITETRUNC, &file_handle->file); if(res != PAL_SUCCESS) { // more informative message will be written after hash conflict will be implemented tr_err("esfs_create_internal() - pal_fsFopen() failed with status 0x%x", (unsigned int)res); goto errorExit; } file_created = 1; if(esfs_cmac_start(file_handle) != ESFS_SUCCESS) { goto errorExit; } cmac_created = 1; // Write the version if(esfs_fwrite_and_calc_cmac(&u16, sizeof(u16), file_handle) != ESFS_SUCCESS) { tr_err("esfs_create_internal() - esfs_fwrite_and_calc_cmac() for esfs version failed"); result = ESFS_ERROR; goto errorExit; } // Write the mode if(esfs_fwrite_and_calc_cmac(&esfs_mode, sizeof(esfs_mode), file_handle) != ESFS_SUCCESS) { tr_err("esfs_create_internal() - esfs_fwrite_and_calc_cmac() for esfs_mode failed"); result = ESFS_ERROR; goto errorExit; } // Header // Write the name length u16 = (uint16_t)name_length; if(esfs_fwrite_and_calc_cmac(&u16, sizeof(u16), file_handle) != ESFS_SUCCESS) { tr_err("esfs_create_internal() - esfs_fwrite_and_calc_cmac() for name_length failed"); result = ESFS_ERROR; goto errorExit; } // Write the name if(esfs_fwrite_and_calc_cmac(name, name_length, file_handle) != ESFS_SUCCESS) { tr_err("esfs_create_internal() - esfs_fwrite_and_calc_cmac() for name failed."); result = ESFS_ERROR; goto errorExit; } // Write the AES nonce, whether the file is encrypted or not. This ensures that the file format is the same // whether encrypted or not. if ((file_handle->esfs_mode & ESFS_ENCRYPTED) != 0) { if(esfs_fwrite_and_calc_cmac((void *)(file_handle->nonce), ESFS_AES_NONCE_SIZE_BYTES, file_handle) != ESFS_SUCCESS) { tr_err("esfs_create_internal() - esfs_fwrite_and_calc_cmac() for AES nonce failed"); result = ESFS_ERROR; goto errorExit; } } // Write the Metadata header // Write the number of items of meta data u16 = (uint16_t)meta_data_qty; if(esfs_fwrite_and_calc_cmac(&u16,sizeof(u16), file_handle) != ESFS_SUCCESS) { tr_err("esfs_create_internal() - esfs_fwrite_and_calc_cmac() for number of items of meta data failed"); result = ESFS_ERROR; goto errorExit; } // If there is meta data if(meta_data_qty != 0) { res = pal_fsFtell(&file_handle->file, &position); if(res != PAL_SUCCESS) { tr_err("esfs_create_internal() - pal_fsFtell() failed with pal_status = 0x%x", (unsigned int)res); result = ESFS_ERROR; goto errorExit; } position += (sizeof(file_handle->tlv_properties.tlv_items[0]) * meta_data_qty); for(i = 0; i < meta_data_qty; i++ ) { file_handle->tlv_properties.tlv_items[i].type = meta_data[i].type; file_handle->tlv_properties.tlv_items[i].length_in_bytes = meta_data[i].length_in_bytes; file_handle->tlv_properties.tlv_items[i].position = (uint16_t)position; // Increment position for next iteration position += meta_data[i].length_in_bytes; } // Write the metadata items result = esfs_fwrite_and_calc_cmac(&file_handle->tlv_properties.tlv_items[0], sizeof(file_handle->tlv_properties.tlv_items[0])*meta_data_qty, file_handle); if(result != ESFS_SUCCESS) { tr_err("esfs_create_internal() - esfs_fwrite_and_calc_cmac() for meta data items failed with esfs result = 0x%x", result); result = ESFS_ERROR; goto errorExit; } // Set the number_of_items field here since it is in use later in this function // when we calculate the file header size file_handle->tlv_properties.number_of_items = meta_data_qty; // Write the Metadata data values // If encrypted esfs is requested (by the esfs_mode argument), then this part should be encrypted for(i = 0; i < meta_data_qty; i++ ) { if((file_handle->esfs_mode & ESFS_ENCRYPTED) != 0) { result = esfs_encrypt_fwrite_and_calc_cmac(meta_data[i].value, meta_data[i].length_in_bytes, file_handle); } else { result = esfs_fwrite_and_calc_cmac(meta_data[i].value, meta_data[i].length_in_bytes, file_handle); } if(result != ESFS_SUCCESS) { tr_err("esfs_create_internal() - esfs_fwrite_and_calc_cmac() for meta data item values failed with esfs result = 0x%x", result); result = ESFS_ERROR; goto errorExit; } } } file_handle->file_flag = ESFS_WRITE; return ESFS_SUCCESS; errorExit: if(file_created) { pal_fsFclose(&file_handle->file); // Clean up if possible. Ignore return value. (void)pal_fsUnlink(full_path_to_create); } if(cmac_created) { uint8_t key[ESFS_CMAC_SIZE_IN_BYTES]; // Clean up cmac. Ignore error. (void)esfs_cmac_finish(file_handle, &key[0]); } return result; } // --------------------------------------------------------------- // API Functions // --------------------------------------------------------------- esfs_result_e esfs_create(const uint8_t *name, size_t name_length, const esfs_tlv_item_t *meta_data, size_t meta_data_qty, uint16_t esfs_mode, esfs_file_t *file_handle) { palStatus_t res = PAL_SUCCESS; esfs_result_e result = ESFS_ERROR; bool is_aes_ctx_created = false; // Verify that the structure is always packed to six bytes, since we read and write it as a whole. PAL_ASSERT_STATIC(sizeof(esfs_tlvItem_t) == 6); // Verify that the array is always packed without padding, since we read and write it as a whole. PAL_ASSERT_STATIC(sizeof(esfs_tlvItem_t[ESFS_MAX_TYPE_LENGTH_VALUES]) == ESFS_MAX_TYPE_LENGTH_VALUES * sizeof(esfs_tlvItem_t)); tr_info("esfs_create - enter"); // Check parameters if (!file_handle || !name || name_length == 0 || name_length > ESFS_MAX_NAME_LENGTH || meta_data_qty > ESFS_MAX_TYPE_LENGTH_VALUES) { tr_err("esfs_create() failed with bad parameters"); result = ESFS_INVALID_PARAMETER; goto errorExit; } // Check the metadata parameter for(size_t meta_data_index = 0; meta_data_index < meta_data_qty; meta_data_index++ ) { if ((!meta_data[meta_data_index].value) || (meta_data[meta_data_index].length_in_bytes == 0)) { tr_err("esfs_create() failed with bad parameters for metadata"); result = ESFS_INVALID_PARAMETER; goto errorExit; } } // If esfs is in encryption mode, make the required initializations if((esfs_mode & ESFS_ENCRYPTED) != 0) { // ** Create AES context for AES encryption res = pal_initAes( &(file_handle->aes_ctx) ); if(res != PAL_SUCCESS) { tr_err("esfs_create() - pal_initAes() failed with pal status 0x%x", (unsigned int)res); result = ESFS_ERROR ; goto errorExit; } is_aes_ctx_created = true; // ** Get AES key from PAL // Note: On each call, PAL should return the same 128 bits key uint8_t aes_key[ESFS_AES_KEY_SIZE_BYTES]; // For AES encryption res = pal_osGetDeviceKey(palOsStorageEncryptionKey128Bit, aes_key, ESFS_AES_KEY_SIZE_BYTES); if(res != PAL_SUCCESS) { tr_err("esfs_create() - pal_osGetDeviceKey() failed with pal status 0x%x", (unsigned int)res); result = ESFS_ERROR ; goto errorExit; } // ** Assign generated AES key to AES context res = pal_setAesKey( file_handle->aes_ctx, aes_key, ESFS_AES_KEY_SIZE_BITS, PAL_KEY_TARGET_ENCRYPTION ); if(res != PAL_SUCCESS) { tr_err("esfs_create() - pal_setAesKey() failed with pal status 0x%x", (unsigned int)res); result = ESFS_ERROR ; goto errorExit; } // ** Generate the AES nonce for AES usage res = pal_osRandomBuffer(file_handle->nonce, ESFS_AES_NONCE_SIZE_BYTES); if(res != PAL_SUCCESS) { tr_err("esfs_create() - pal_osRandomBuffer() failed with pal status 0x%x", (unsigned int)res); result = ESFS_ERROR ; goto errorExit; } } // We set the blob_name_length field here because it is in use later in this function when we calculate the file header size. // Since this field is also used to check the file handle validity [ esfs_validate() ] we set it to zero on an error exit. file_handle->blob_name_length = name_length; file_handle->esfs_mode = esfs_mode; file_handle->file_invalid = 0; file_handle->tlv_properties.number_of_items = 0; // Indicate that there is not a signature context yet. file_handle->signature_ctx = 0; file_handle->data_size = 0; if (esfs_get_name_from_blob(name, name_length, file_handle->short_file_name, ESFS_FILE_NAME_LENGTH) != ESFS_SUCCESS) { tr_err("esfs_create() - esfs_get_name_from_blob() failed"); goto errorExit; } // Put working file name in file_full_path char file_full_path[MAX_FULL_PATH_SIZE]; res = pal_fsGetMountPoint(PAL_FS_PARTITION_PRIMARY, PAL_MAX_FOLDER_DEPTH_CHAR + 1, file_full_path); if (res != PAL_SUCCESS) { tr_err("esfs_create() - pal_fsGetMountPoint() for working directory failed with pal_status = 0x%x", (unsigned int)res); result = ESFS_ERROR; goto errorExit; } strncat(file_full_path, "/" ESFS_WORKING_DIRECTORY "/", sizeof(ESFS_WORKING_DIRECTORY) + 1); strncat(file_full_path, file_handle->short_file_name, ESFS_QUALIFIED_FILE_NAME_LENGTH - 1); // Check if the file exists in the working directory (not acceptable) // Note that this is just a check. We will only actually open the file later (in esfs_create_internal()). res = pal_fsFopen(file_full_path, PAL_FS_FLAG_READONLY, &file_handle->file); if (res == PAL_SUCCESS) { result = ESFS_EXISTS; file_handle->esfs_mode = 0; // result can be ESFS_HASH_CONFLICT or ESFS_WRONG_FILE_VERSION // Check if there is a different name in the file // Check that the name written inside the file is the same as that given. If not // you should choose a different name. esfs_result_e check_result = esfs_check_file_validity(name, name_length, file_handle); if (check_result == ESFS_HASH_CONFLICT || check_result == ESFS_INVALID_FILE_VERSION) { result = check_result; } pal_fsFclose(&file_handle->file); tr_err("esfs_create() - pal_fsFopen() for working dir file failed"); goto errorExit; } // If factory reset file then we make some checks if (esfs_mode & (uint16_t)ESFS_FACTORY_VAL) { // Put backup folder name in file_full_path res = pal_fsGetMountPoint(PAL_FS_PARTITION_SECONDARY, PAL_MAX_FOLDER_DEPTH_CHAR + 1, file_full_path); if (res != PAL_SUCCESS) { tr_err("esfs_create() - pal_fsGetMountPoint() for backup directory failed with pal_status = 0x%x", (unsigned int)res); result = ESFS_ERROR; goto errorExit; } strncat(file_full_path, "/" ESFS_BACKUP_DIRECTORY, sizeof(ESFS_BACKUP_DIRECTORY)); // Create the esfs subfolder for backup res = pal_fsMkDir(file_full_path); if (res != PAL_SUCCESS) { // Any error apart from file exist returns error. if (res != PAL_ERR_FS_NAME_ALREADY_EXIST) { tr_err("esfs_create() - pal_fsMkDir() for backup dir failed with pal status 0x%x", (unsigned int)res); goto errorExit; } } // Put backup file name in file_full_path strcat(file_full_path, "/"); strncat(file_full_path, file_handle->short_file_name, ESFS_QUALIFIED_FILE_NAME_LENGTH - 1); // Check if the file exists in esfs backup directory (acceptable unless there is a hash conflict for the name) res = pal_fsFopen(file_full_path, PAL_FS_FLAG_READONLY, &file_handle->file); if (res == PAL_SUCCESS) { file_handle->esfs_mode = 0; // result can be ESFS_HASH_CONFLICT or ESFS_WRONG_FILE_VERSION // Check if there is a different name in the file // Check that the name written inside the file is the same as that given. If not // you should choose a different name. esfs_result_e check_result = esfs_check_file_validity(name, name_length, file_handle); // Close the file. pal_fsFclose(&file_handle->file); if (check_result == ESFS_HASH_CONFLICT || check_result == ESFS_INVALID_FILE_VERSION) { tr_err("esfs_create() - esfs_check_file_validity() failed with status 0x%x", check_result); result = check_result; goto errorExit; } // It is OK for it to exist, so continue. } } // Now we actually create the new file. // file_full_path contains the correct location (working/backup) result = esfs_create_internal( name, name_length, meta_data, meta_data_qty, esfs_mode, file_handle, // Working or backup file_full_path ); if(result != ESFS_SUCCESS) { goto errorExit; } return ESFS_SUCCESS; errorExit: // Invalidate blob_name_length filed since it is used to check the file handle validity [ esfs_validate() ] if(file_handle != NULL) { file_handle->blob_name_length = 0; } if(is_aes_ctx_created) { pal_freeAes( &(file_handle->aes_ctx) ); } return result; } esfs_result_e esfs_open(const uint8_t *name, size_t name_length, uint16_t *esfs_mode, esfs_file_t *file_handle) { esfs_result_e result = ESFS_ERROR; uint16_t file_opened = 0; uint16_t cmac_created = 0; bool is_aes_ctx_created = false; palStatus_t res = PAL_SUCCESS; tr_info("esfs_open - enter"); // Check parameters if(!file_handle || !name || name_length == 0 || name_length > ESFS_MAX_NAME_LENGTH) { tr_err("esfs_open() failed with bad parameters"); result = ESFS_INVALID_PARAMETER; goto errorExit; } char working_dir_path[MAX_FULL_PATH_SIZE]; res = pal_fsGetMountPoint(PAL_FS_PARTITION_PRIMARY, PAL_MAX_FOLDER_DEPTH_CHAR + 1, working_dir_path); if (res != PAL_SUCCESS) { tr_err("esfs_open() - pal_fsGetMountPoint() for working directory failed with pal_status = 0x%x", (unsigned int)res); return ESFS_ERROR; } strncat(working_dir_path, "/" ESFS_WORKING_DIRECTORY "/", sizeof(ESFS_WORKING_DIRECTORY) + 1); // This is used to esfs_validate the file handle so we set it to zero here and only when open // succeeds to the real value. file_handle->blob_name_length = 0; file_handle->file_invalid = 0; memset(&file_handle->cmac[0], 0, sizeof(file_handle->cmac)); if(esfs_get_name_from_blob(name, name_length, file_handle->short_file_name, ESFS_FILE_NAME_LENGTH) != ESFS_SUCCESS) { tr_err("esfs_open() - esfs_get_name_from_blob() failed"); result = ESFS_ERROR; goto errorExit; } strncat(working_dir_path, file_handle->short_file_name, ESFS_QUALIFIED_FILE_NAME_LENGTH - 1); // Open the file read only res = pal_fsFopen(working_dir_path, PAL_FS_FLAG_READONLY, &file_handle->file); if(res != PAL_SUCCESS) { // tr_err("esfs_open() - pal_fsFopen() for working dir file failed with pal_status = 0x%x", (unsigned int)res); // File cannot be opened so return an error result = ESFS_NOT_EXISTS; goto errorExit; } file_opened = 1; if(esfs_cmac_start(file_handle) != ESFS_SUCCESS) { goto errorExit; } cmac_created = 1; // Check that the name written inside the file is the same as that given // Note: After this call, the read position will be set to the point after the "Name Blob" result = esfs_check_file_validity(name, name_length, file_handle); if(result != ESFS_SUCCESS) { // the requested file not exists, but exists file with the same short name if (result == ESFS_HASH_CONFLICT) { result = ESFS_NOT_EXISTS; } tr_err("esfs_open() - esfs_check_file_validity() failed with status = 0x%x", result); // File cannot be opened so return an error goto errorExit; } if (esfs_mode) { *esfs_mode = file_handle->esfs_mode; // file_handle->esfs_mode was set by esfs_check_file_validity() } // If esfs is in encryption mode, make the required initializations if((file_handle->esfs_mode & ESFS_ENCRYPTED) != 0) { // ** Create AES context for AES decryption res = pal_initAes( &(file_handle->aes_ctx) ); if(res != PAL_SUCCESS) { tr_err("esfs_open() - pal_initAes() failed with status 0x%x", (unsigned int)res); result = ESFS_ERROR ; goto errorExit; } is_aes_ctx_created = true; // ** Get AES key from PAL // Note: On each call, PAL should return the same 128 bits key uint8_t aes_key[ESFS_AES_KEY_SIZE_BYTES]; res = pal_osGetDeviceKey(palOsStorageEncryptionKey128Bit, aes_key, ESFS_AES_KEY_SIZE_BYTES); if(res != PAL_SUCCESS) { tr_err("esfs_open() - pal_osGetDeviceKey() failed with status 0x%x", (unsigned int)res); result = ESFS_ERROR ; goto errorExit; } // ** Assign generated AES key to AES context res = pal_setAesKey( file_handle->aes_ctx, aes_key, ESFS_AES_KEY_SIZE_BITS, PAL_KEY_TARGET_ENCRYPTION ); if(res != PAL_SUCCESS) { tr_err("esfs_open() - pal_setAesKey() failed with status 0x%x", (unsigned int)res); result = ESFS_ERROR; goto errorExit; } } size_t num_bytes; // ** Read the AES nonce into file_handle->nonce if ((file_handle->esfs_mode & ESFS_ENCRYPTED) != 0) { result = esfs_cmac_read(file_handle, &file_handle->nonce[0], ESFS_AES_NONCE_SIZE_BYTES, &num_bytes); if((result != ESFS_SUCCESS) || (num_bytes != ESFS_AES_NONCE_SIZE_BYTES)) { tr_err("esfs_open() - esfs_cmac_read() (AES nonce) failed with ESFS result = 0x%x and num_bytes bytes = %zu", (unsigned int)result, num_bytes); result = ESFS_ERROR; goto errorExit; } } file_handle->tlv_properties.number_of_items = 0; // Read the number of items of meta data uint16_t meta_data_qty; result = esfs_cmac_read(file_handle, (void *)( &meta_data_qty ), sizeof(meta_data_qty), &num_bytes); if(result != ESFS_SUCCESS || num_bytes != sizeof(meta_data_qty) || meta_data_qty > ESFS_MAX_TYPE_LENGTH_VALUES) { tr_err("esfs_open() - esfs_cmac_read() (number of items of meta data) failed with ESFS result = 0x%x and num_bytes bytes = %zu", (unsigned int)result, num_bytes); result = ESFS_ERROR; goto errorExit; } // Read the metadata properties if there are any if(meta_data_qty != 0) { result = esfs_cmac_read( file_handle, (void *) ( &(file_handle->tlv_properties.tlv_items[0]) ), (sizeof(file_handle->tlv_properties.tlv_items[0]) * meta_data_qty), &num_bytes ); if(result != ESFS_SUCCESS || num_bytes != sizeof(file_handle->tlv_properties.tlv_items[0])*meta_data_qty) { tr_err("esfs_open() - esfs_cmac_read() (metadata properties) failed with ESFS result = 0x%x and num_bytes bytes = %zu", (unsigned int)result, num_bytes); goto errorExit; } // Skip to the start of the data by calculating the last metadata position plus its length esfs_tlvItem_t *ptypeLengthValueItem = &file_handle->tlv_properties.tlv_items[meta_data_qty - 1]; if(esfs_cmac_skip_to(file_handle, ptypeLengthValueItem->position + ptypeLengthValueItem->length_in_bytes) != ESFS_SUCCESS) { tr_err("esfs_open() - esfs_cmac_skip_to() failed."); result = ESFS_ERROR; goto errorExit; } } file_handle->tlv_properties.number_of_items = meta_data_qty; // We are at the start of the data section file_handle->current_read_pos = 0; // Get current position int32_t current_pos; res = pal_fsFtell(&file_handle->file, ¤t_pos); if (res != PAL_SUCCESS) { tr_err("esfs_open() - pal_fsFtell() failed with pal_status = 0x%x", (unsigned int)res); goto errorExit; } // get the whole file size and store it in the handle res = esfs_get_physical_file_size(&file_handle->file, &file_handle->file_size); if (res != PAL_SUCCESS) { tr_err("esfs_open() - esfs_open() failed with status 0x%x", (unsigned int)res); result = ESFS_ERROR; goto errorExit; } // Skip to the end of the file while calculating the cmac if(esfs_cmac_skip_to(file_handle, file_handle->file_size - ESFS_CMAC_SIZE_IN_BYTES) != ESFS_SUCCESS) { tr_err("esfs_open() - esfs_cmac_skip_to() failed."); result = ESFS_ERROR; goto errorExit; } // Terminate cmac calculation and get it. unsigned char cmac[ESFS_CMAC_SIZE_IN_BYTES]; if(esfs_cmac_finish(file_handle, &cmac[0]) != ESFS_SUCCESS) { tr_err("esfs_open() - esfs_finish_cmac() failed"); goto errorExit; } cmac_created = 0; // save the CMAC in the file descriptor. We will use this to check that the file has not // changed when esfs_read() or read_meta_data() is called. memcpy(&file_handle->cmac[0],&cmac[0],sizeof(file_handle->cmac)); // Check the cmac and set the file position to the start of the data if(esfs_cmac_check_and_restore(file_handle, &cmac[0], current_pos) != ESFS_SUCCESS) { tr_err("esfs_open() - cmac that we read from the file does not match the one that we calculated"); result = ESFS_CMAC_DOES_NOT_MATCH; goto errorExit; } // Calculate the size of the data only, by getting the file size and deducting the header and cmac file_handle->data_size = file_handle->file_size - esfs_file_header_size(file_handle); // We deduct the cmac bytes at the end of the file since they are not part of the data file_handle->data_size -= ESFS_CMAC_SIZE_IN_BYTES; file_handle->file_flag = ESFS_READ; file_handle->blob_name_length = name_length; return ESFS_SUCCESS; errorExit: if(file_opened) { pal_fsFclose(&file_handle->file); } if(is_aes_ctx_created) { pal_freeAes( &(file_handle->aes_ctx) ); } if(cmac_created) { // Clean up cmac. Ignore error. (void)esfs_cmac_finish(file_handle, &cmac[0]); } return result; } esfs_result_e esfs_write(esfs_file_t *file_handle, const void *buffer, size_t bytes_to_write) { esfs_result_e result = ESFS_ERROR; tr_info("esfs_write - enter"); if((esfs_validate(file_handle) != ESFS_SUCCESS) || (!buffer) || (bytes_to_write == 0)) { tr_err("esfs_write() failed with bad parameters"); return ESFS_INVALID_PARAMETER; } if(file_handle->file_flag == ESFS_READ) { tr_err("esfs_write() write failed - file is opened for read only"); result = ESFS_FILE_OPEN_FOR_READ; goto errorExit; } else { // Write data // If encrypted esfs is requested (file_handle->esfs_mode), then this part should be encrypted // The data should be encrypted if the encrypted esfs is requested by the esfs_mode argument if((file_handle->esfs_mode & ESFS_ENCRYPTED) != 0) { result = esfs_encrypt_fwrite_and_calc_cmac(buffer, bytes_to_write, file_handle); } else { result = esfs_fwrite_and_calc_cmac(buffer, bytes_to_write, file_handle); } if(result != ESFS_SUCCESS) { tr_err("esfs_write() - esfs_fwrite_and_calc_cmac()/esfs_encrypt_fwrite_and_calc_cmac() for data failed with esfs result = 0x%x", result); // Since the write failed, we cannot be sure of the state of the file, so we mark it as invalid. file_handle->file_invalid = 1; result = ESFS_ERROR; goto errorExit; } } file_handle->data_size += bytes_to_write; return ESFS_SUCCESS; errorExit: return result; } esfs_result_e esfs_read(esfs_file_t *file_handle, void *buffer, size_t bytes_to_read, size_t *read_bytes) { esfs_result_e result = ESFS_ERROR; uint16_t cmac_created = 0; size_t remaining_bytes = 0; palStatus_t res = PAL_SUCCESS; tr_info("esfs_read - enter"); if(esfs_validate(file_handle) != ESFS_SUCCESS || read_bytes == NULL || !buffer) { result = ESFS_INVALID_PARAMETER; goto errorExit; } if(file_handle->file_flag != ESFS_READ) { result = ESFS_FILE_OPEN_FOR_WRITE; goto errorExit; } // Save file position int32_t position; res = pal_fsFtell(&file_handle->file, &position); if(res != PAL_SUCCESS) { tr_err("esfs_read() - pal_fsFtell() failed with pal status 0x%x", (unsigned int)res); goto errorExit; } // Limit how many bytes we can actually read depending on the size of the data section. remaining_bytes = file_handle->data_size - file_handle->current_read_pos; bytes_to_read = PAL_MIN(remaining_bytes, bytes_to_read); if(esfs_cmac_start(file_handle) != ESFS_SUCCESS) { goto errorExit; } cmac_created = 1; if(esfs_cmac_skip_to(file_handle, position) != ESFS_SUCCESS) { goto errorExit; } // Read data // If required according to esfs_mode, the read data will be decrypted size_t num_bytes; if((file_handle->esfs_mode & ESFS_ENCRYPTED) != 0) { if(esfs_read_and_decrypt(file_handle, buffer, bytes_to_read, &num_bytes) != ESFS_SUCCESS) { goto errorExit; } } else { if(esfs_cmac_read(file_handle, buffer, bytes_to_read, &num_bytes ) != ESFS_SUCCESS) { goto errorExit; } } *read_bytes = num_bytes; if(esfs_cmac_skip_to(file_handle ,file_handle->file_size - ESFS_CMAC_SIZE_IN_BYTES) != ESFS_SUCCESS) { goto errorExit; } unsigned char cmac[ESFS_CMAC_SIZE_IN_BYTES]; if(esfs_cmac_finish(file_handle, &cmac[0]) != ESFS_SUCCESS) { tr_err("esfs_read() - esfs_finish_cmac() failed"); goto errorExit; } cmac_created = 0; // Check the cmac and set to the byte after the end of the data being read. if(esfs_cmac_check_and_restore(file_handle, &cmac[0], position + num_bytes) != ESFS_SUCCESS) { tr_err("esfs_read() - cmac that we read from the file does not match the one that we calculated"); result = ESFS_CMAC_DOES_NOT_MATCH; goto errorExit; } // Update the current position file_handle->current_read_pos += num_bytes; return ESFS_SUCCESS; errorExit: tr_err("esfs_read errorExit result=0x%x", result); if(cmac_created) { // Clean up cmac. Ignore error and resulting cmac. (void)esfs_cmac_finish(file_handle, &cmac[0]); } return result; } esfs_result_e esfs_seek(esfs_file_t *file_handle, int32_t offset, esfs_seek_origin_e whence, uint32_t *position) { esfs_result_e result = ESFS_ERROR; palStatus_t res = PAL_SUCCESS; tr_info("esfs_seek - enter"); if(esfs_validate(file_handle) != ESFS_SUCCESS) { tr_err("esfs_seek() failed with bad parameters"); return ESFS_INVALID_PARAMETER; } if(file_handle->file_flag != ESFS_READ) { tr_err("esfs_seek() seek failed - file is opened for write only"); result = ESFS_FILE_OPEN_FOR_WRITE; goto errorExit; } pal_fsOffset_t pal_whence; // ESFS whence enum values are in sync with those of pal if(whence == ESFS_SEEK_SET) { if(offset > (int32_t)file_handle->data_size || offset < 0) { tr_err("esfs_seek() failed with bad parameters in offset calculation : ESFS_SEEK_SET"); result = ESFS_INVALID_PARAMETER; goto errorExit; } // Add the offset to the start of the data offset += esfs_file_header_size(file_handle); pal_whence = PAL_FS_OFFSET_SEEKSET; } else if(whence == ESFS_SEEK_END) { if(offset < -(int32_t)file_handle->data_size || offset > 0) { tr_err("esfs_seek() failed with bad parameters in offset calculation : ESFS_SEEK_END"); result = ESFS_INVALID_PARAMETER; goto errorExit; } // Deduct the cmac size from the offset because it is located after the data section. offset -= ESFS_CMAC_SIZE_IN_BYTES; pal_whence = PAL_FS_OFFSET_SEEKEND; } else if(whence == ESFS_SEEK_CUR) { if(offset + file_handle->current_read_pos > (int32_t)file_handle->data_size || offset + (int32_t)file_handle->current_read_pos < 0) { tr_err("esfs_seek() failed with bad parameters in offset calculation : ESFS_SEEK_CUR"); result = ESFS_INVALID_PARAMETER; goto errorExit; } pal_whence = PAL_FS_OFFSET_SEEKCUR; } else { tr_err("esfs_seek() failed with bad parameters - wrong whence"); result = ESFS_INVALID_PARAMETER; goto errorExit; } res = pal_fsFseek(&file_handle->file, offset, pal_whence); if(res != PAL_SUCCESS) { tr_err("esfs_seek() - pal_fsFseek() failed with pal status 0x%x", (unsigned int)res); goto errorExit; } // Get current position if position is not NULL if(position) { res = pal_fsFtell(&file_handle->file, (int32_t *)position); if(res != PAL_SUCCESS) { tr_err("esfs_seek() - pal_fsFtell() failed with pal status 0x%x", (unsigned int)res); goto errorExit; } // Ignore the file header data *position -= esfs_file_header_size(file_handle); // Update the current position file_handle->current_read_pos = *position; } return ESFS_SUCCESS; errorExit: return result; } esfs_result_e esfs_file_size(esfs_file_t *file_handle, size_t *size_in_bytes) { esfs_result_e result = ESFS_ERROR; tr_info("esfs_file_size - enter"); if((esfs_validate(file_handle) != ESFS_SUCCESS) || (!size_in_bytes)) { tr_err("esfs_file_size() failed with bad parameters"); result = ESFS_INVALID_PARAMETER; goto errorExit; } *size_in_bytes = file_handle->data_size; return ESFS_SUCCESS; errorExit: return result; } esfs_result_e esfs_close(esfs_file_t *file_handle) { uint16_t failed_to_write_CMAC = 0; uint16_t file_esfs_mode = 0; esfs_file_flag_e esfs_file_flag = 0; char esfs_short_file_name[ESFS_QUALIFIED_FILE_NAME_LENGTH] = {0}; esfs_result_e result = ESFS_ERROR; char full_path_working_dir[MAX_FULL_PATH_SIZE]; palStatus_t res = PAL_SUCCESS; tr_info("esfs_close - enter"); if(esfs_validate(file_handle) != ESFS_SUCCESS) { tr_err("esfs_close() failed with bad parameters"); result = ESFS_INVALID_PARAMETER; goto errorExit; } res = pal_fsGetMountPoint(PAL_FS_PARTITION_PRIMARY, PAL_MAX_FOLDER_DEPTH_CHAR + 1, full_path_working_dir); if (res != PAL_SUCCESS) { tr_err("esfs_close() - pal_fsGetMountPoint() for working directory failed with pal_status = 0x%x", (unsigned int)res); result = ESFS_ERROR; goto errorExit; } strncat(full_path_working_dir, "/" ESFS_WORKING_DIRECTORY "/", sizeof(ESFS_WORKING_DIRECTORY) + 1); // Close AES context if needed if((file_handle->esfs_mode & ESFS_ENCRYPTED) != 0) { pal_freeAes( &(file_handle->aes_ctx) ); } esfs_file_flag = file_handle->file_flag; file_esfs_mode = file_handle->esfs_mode; strncpy(esfs_short_file_name, file_handle->short_file_name, ESFS_QUALIFIED_FILE_NAME_LENGTH - 1); if(file_handle->file_flag == ESFS_WRITE) { unsigned char cmac[ESFS_CMAC_SIZE_IN_BYTES]; // Finish signature calculation if(esfs_cmac_finish(file_handle, &cmac[0]) != ESFS_SUCCESS) { tr_err("esfs_close() - esfs_cmac_finish() failed"); goto errorExit; } // Write signature size_t bytes_written; res = pal_fsFwrite(&file_handle->file, &cmac[0], sizeof(cmac), &bytes_written); if(res != PAL_SUCCESS || sizeof(cmac) != bytes_written) { tr_err("esfs_close() - pal_fsFwrite() (signature) failed with pal result = 0x%x and bytes_written bytes = %zu", (unsigned int)res, bytes_written); // mark the file invalid on a failed write file_handle->file_invalid = 1; // Continue so that we delete the file, but we should return failure later failed_to_write_CMAC = 1; } } res = pal_fsFclose(&file_handle->file); if(res == PAL_SUCCESS) { // Remove a file that is invalid. It may have become invalid due to a failed write. if(file_handle->file_invalid) { strncat(full_path_working_dir,file_handle->short_file_name, ESFS_QUALIFIED_FILE_NAME_LENGTH - 1); res = pal_fsUnlink(full_path_working_dir); if(res != PAL_SUCCESS) { tr_err("esfs_close() - pal_fsUnlink() failed with pal status 0x%x", (unsigned int)res); goto errorExit; } } } else { tr_err("esfs_close() - pal_fsFclose() failed with pal status 0x%x", (unsigned int)res); goto errorExit; } if(failed_to_write_CMAC) { goto errorExit; } if ((file_esfs_mode & ESFS_FACTORY_VAL) && (esfs_file_flag == ESFS_WRITE) && !(file_handle->file_invalid)) { char full_path_backup_dir[MAX_FULL_PATH_SIZE] = { 0 }; res = pal_fsGetMountPoint(PAL_FS_PARTITION_SECONDARY, PAL_MAX_FOLDER_DEPTH_CHAR + 1, full_path_backup_dir); if (res != PAL_SUCCESS) { tr_err("esfs_close() - pal_fsGetMountPoint() for backup directory failed with pal_status = 0x%x", (unsigned int)res); result = ESFS_ERROR; goto errorExit; } strncat(full_path_backup_dir, "/" ESFS_BACKUP_DIRECTORY "/", sizeof(ESFS_BACKUP_DIRECTORY) + 1); strncat(full_path_working_dir, esfs_short_file_name, ESFS_QUALIFIED_FILE_NAME_LENGTH -1); strncat(full_path_backup_dir, esfs_short_file_name, ESFS_QUALIFIED_FILE_NAME_LENGTH - 1); if (esfs_copy_file(full_path_backup_dir, full_path_working_dir) != ESFS_SUCCESS) { tr_err("esfs_close() - esfs_copy_file() failed"); goto errorExit; } } return ESFS_SUCCESS; errorExit: return result; } esfs_result_e esfs_delete(const uint8_t *name, size_t name_length) { palStatus_t pal_result = PAL_SUCCESS; char working_dir_path[MAX_FULL_PATH_SIZE] = { 0 }; char short_file_name[ESFS_QUALIFIED_FILE_NAME_LENGTH]; esfs_result_e result = ESFS_ERROR; tr_info("esfs_delete - enter"); // Check parameters if(!name || name_length == 0) { tr_err("esfs_delete() failed with bad parameters"); result = ESFS_INVALID_PARAMETER; goto errorExit; } if(esfs_get_name_from_blob(name, name_length, short_file_name, ESFS_FILE_NAME_LENGTH ) != ESFS_SUCCESS) { tr_err("esfs_delete() - esfs_get_name_from_blob() failed"); goto errorExit; } tr_info("esfs_delete %s", short_file_name); pal_result = pal_fsGetMountPoint(PAL_FS_PARTITION_PRIMARY, PAL_MAX_FOLDER_DEPTH_CHAR + 1, working_dir_path); if (pal_result != PAL_SUCCESS) { tr_err("esfs_delete() - pal_fsGetMountPoint() for working directory failed with pal_status = 0x%x", (unsigned int)pal_result); result = ESFS_ERROR; goto errorExit; } strncat(working_dir_path, "/" ESFS_WORKING_DIRECTORY "/", sizeof(ESFS_WORKING_DIRECTORY) + 1); // We do not verify that name is the actual name in the file because currently we do not allow the situation of hash // clash to arise. strncat(working_dir_path,short_file_name, ESFS_QUALIFIED_FILE_NAME_LENGTH - 1); tr_info("esfs_delete %s", working_dir_path); pal_result = pal_fsUnlink(working_dir_path); if ((pal_result == PAL_ERR_FS_NO_FILE) || (pal_result == PAL_ERR_FS_NO_PATH)) { tr_err("esfs_delete() - pal_fsUnlink() failed with pal status 0x%x", (unsigned int)pal_result); result = ESFS_NOT_EXISTS; goto errorExit; } else if (pal_result != PAL_SUCCESS) { tr_err("esfs_delete() - pal_fsUnlink() failed with pal status 0x%x", (unsigned int)pal_result); goto errorExit; } return ESFS_SUCCESS; errorExit: return result; } esfs_result_e esfs_get_meta_data_properties(esfs_file_t *file_handle, esfs_tlv_properties_t **meta_data_properties) { esfs_result_e result = ESFS_ERROR; tr_info("esfs_get_meta_data_properties - enter"); if((esfs_validate(file_handle) != ESFS_SUCCESS) || (!meta_data_properties)) { tr_err("esfs_get_meta_data_properties() failed with bad parameters"); result = ESFS_INVALID_PARAMETER; goto errorExit; } if (file_handle->file_flag != ESFS_READ) { tr_err("esfs_get_meta_data_properties() failed - file is opened for write only"); result = ESFS_FILE_OPEN_FOR_WRITE; goto errorExit; } *meta_data_properties = &file_handle->tlv_properties; return ESFS_SUCCESS; errorExit: return result; } esfs_result_e esfs_read_meta_data(esfs_file_t *file_handle, uint32_t index, esfs_tlv_item_t *meta_data) { esfs_result_e result = ESFS_ERROR; bool is_read_error = false; uint16_t cmac_created = 0; int32_t offset_to_restore = 0; palStatus_t res = PAL_SUCCESS; tr_info("esfs_read_meta_data - enter"); if(esfs_validate(file_handle) != ESFS_SUCCESS || index >= ESFS_MAX_TYPE_LENGTH_VALUES || !meta_data || (file_handle->tlv_properties.tlv_items[index].length_in_bytes == 0)) { tr_err("esfs_read_meta_data() failed with bad parameters"); result = ESFS_INVALID_PARAMETER; goto errorExit; } if(file_handle->file_flag != ESFS_READ) { tr_err("esfs_read_meta_data() failed - file is opened for write only"); result = ESFS_FILE_OPEN_FOR_WRITE; goto errorExit; } // Get current file position int32_t current_pos; res = pal_fsFtell(&file_handle->file, ¤t_pos); if(res != PAL_SUCCESS) { tr_err("esfs_read_meta_data() - pal_fsFtell() failed with pal status 0x%x", (unsigned int)res); goto errorExit; } // Start the cmac calculation and position to the start of the file if(esfs_cmac_start(file_handle) != ESFS_SUCCESS) { goto errorExit; } cmac_created = 1; // Skip to the meta-data position while calculating the cmac if(esfs_cmac_skip_to(file_handle, file_handle->tlv_properties.tlv_items[index].position) != ESFS_SUCCESS) { tr_err("esfs_read_meta_data() - pal_fsFseek() failed with pal status 0x%x", (unsigned int)res); goto errorExit; } // Read data // If required according to esfs_mode, the read data will be decrypted size_t num_bytes; if((file_handle->esfs_mode & ESFS_ENCRYPTED) != 0) { if(esfs_read_and_decrypt( file_handle, meta_data->value, file_handle->tlv_properties.tlv_items[index].length_in_bytes, &num_bytes ) != ESFS_SUCCESS) { is_read_error = true; } } else { if(esfs_cmac_read(file_handle, meta_data->value, file_handle->tlv_properties.tlv_items[index].length_in_bytes, &num_bytes ) != ESFS_SUCCESS) { is_read_error = true; } } if(is_read_error || (num_bytes != file_handle->tlv_properties.tlv_items[index].length_in_bytes)) { tr_err("esfs_read_meta_data() - read data failed is_read_error = %s and num_bytes = %zu", is_read_error ? "true" : "false", num_bytes); goto errorExit; } // Skip to the end of the data section of the file. if(esfs_cmac_skip_to(file_handle ,file_handle->file_size - ESFS_CMAC_SIZE_IN_BYTES) != ESFS_SUCCESS) { goto errorExit; } // Return the cmac unsigned char cmac[ESFS_CMAC_SIZE_IN_BYTES]; if(esfs_cmac_finish(file_handle, &cmac[0]) != ESFS_SUCCESS) { tr_err("esfs_read() - esfs_finish_cmac() failed"); goto errorExit; } cmac_created = 0; // Before restoring old position, make sure offset_to_restore is not a negative number offset_to_restore = current_pos; if(offset_to_restore < 0) { tr_err("esfs_read_meta_data() failed - current_pos is negative"); goto errorExit; } // Check the cmac and restore the file position to the saved position if(esfs_cmac_check_and_restore(file_handle, &cmac[0], offset_to_restore) != ESFS_SUCCESS) { tr_err("esfs_read_meta_data() - cmac that we read from the file does not match the one that we calculated"); result = ESFS_CMAC_DOES_NOT_MATCH; goto errorExit; } // Update meta_data fields meta_data->type = file_handle->tlv_properties.tlv_items[index].type; meta_data->length_in_bytes = file_handle->tlv_properties.tlv_items[index].length_in_bytes; return ESFS_SUCCESS; errorExit: if(cmac_created) { // Clean up cmac. Ignore error. (void)esfs_cmac_finish(file_handle, &cmac[0]); } return result; }