Introduction
IoT devices are becoming more common in data sensitive applications such as medical monitoring. Such devices are exposed to the following types of attack vectors: internet, LAN, and physical access. The SimpleLink™ Wi-Fi® CC3x20, CC3x35 device family offers security features which mitigate vulnerability from these attack vectors. These security features include: secure file system, secure socket, secure http server, secure content delivery, and secure manufacturing.
Vulnerabilities of IoT Devices
IoT devices are typically connected to the internet and often provide access to personal/sensitive information. Some common attack vectors include:
- Internet Network Connectivity Vector: The vulnerabilities in this vector could come from all communication channels (ex. sockets) and protocols in use. In general, these attacks target all the resources and information that pass through these communication channels.
- Local Network Connectivity Vector: This attack vector is similar to the internet network connectivity vector but with a much narrower geographical scope. The attackers could attempt to breach security with attacks that are based on monitoring the traffic of the wireless network. Monitoring a wireless network is fairly easy, and even in secured wireless networks some of the frame headers are not encrypted.
- Physical Access (Operation): The operation vector refers to attacks that enable the hacker to control the operation of the device by using the interfaces that the final product offers (ex. power line, buttons, etc.).
- Physical Access (PCB Manipulation): The manipulation vector relates to the ability of the hacker to have access to the actual board allowing an individual to monitor the lines and interfaces or even manipulate wires.
SimpleLink Security Features
In addition to the secure sockets, SimpleLink Wi-Fi includes the following security features:
- Secure Networking: Ensures the authenticity, reliability, and confidentiality of data flow between peers over the network
- Wi-Fi Security: The Wi-Fi layer of the device supports security to ensure the integrity and confidentiality of L2 transaction frames between AP and station, or between two peers in Wi-Fi Direct mode
- Secure File System: File system security for confidentiality and integrity of data (including: authentication and integratity verification, content encryption, cloning protection, failsafe update mechanism, factory image recovery)
- Secure Content Delivery: This capability enables the exchange of confidential content to the device and provides another level of security at the application-layer
- Crypto Utilities: The SimpleLink device exposes a set of crypto primitives to aid in some common cryptographic related operations
- Hardware Crypto Engine: The CC32xx microcontroller includes a set of hardware crypto engines such as CRC, AES, DES and SHA/MD5, known as DTHE (Data Transformation and Hash Engine), to enhance the performance of applications that require custom application level security
Prerequisites
Software
- Code Composer Studio v8.3 or newer
- SimpleLink Wi-Fi CC32xx Wireless MCUs support installed
- Make sure that CCS is using the latest updates: Help → Check for Updates
- CC32xx SDK v2.40.00.05 or newer
- UniFlash v4.6.0 or newer
- Terminal emulator program such as TeraTerm or PuTTY
Hardware
- 1x CC32xx LaunchPad
- CC3235S LaunchPad (LAUNCHXL-CC3235S)
- CC3235SF LaunchPad (LAUNCHXL-CC3235SF)
- CC3220S LaunchPad (CC3220S-LAUNCHXL)
- CC3220SF LaunchPad (CC3220SF-LAUNCHXL)
- 1x Micro-USB cable (included with LaunchPad)
File System Overview
The second generation of SimpleLink Wi-Fi devices uses an externally attached NVM (non-volatile memory) in the form of an SFLASH (serial flash device) to store a range of sensitive information such as passkeys, device configurations, and TI/customer intellectual property. The SimpleLink solution utilizes a file system to organize data on the SFLASH which is accessible using a simple host interface.
The file system provides security for the user and system files while also securing the data structures of the file system itself. The following security functionalities are related to the CC3120, CC3135, CC3220S, CC3235S, CC3220SF and CC3235SF devices:
File System Funcionality
Encryption
- Files can be kept encrypted on the serial flash using the AES128-CTR encryption cryptographic standard. Decryption is done automatically when reading the file.
- Keys are generated by the device using a true random number generator hardware engine.
- Some of the system files are also encrypted including the files that contain sensitive information such as keys.
Cloning Protection
- Secure files can be read only by the device that created them.
- The device enters into a lock state if the content of the serial flash has been copied to another device, or the serial flash has been assembled into a different device from the one that programmed it.
Integrity Verification
- As part of an integrity test, the device generates an encrypted string of the file content every time it is changed by the file system. The result is kept as the file signature. Once the file is opened for read, the file content is tested against this signature. If the device detects that the content of a file was changed by unauthorized user, it triggers a system alert.
- System files and the file system structure are also protected and their integrity is tested by the device. The integrity validation of system files is done by using the HMAC-SHA2 (256) algorithm. If the device detects content change by an unauthorized user in a system file or in a file system structure, it enters a lock state.
- The SimpleLink device ensures system integrity during programming and downloading updates by providing:
- Image Programming: A procedure that uses a single image file to ensure the device is not partially configured
- Files Bundle: Used during Over-The-Air programming to allow the user to modify files and approve new content as one operation or rollback to former content
Access Control
- Each secure file has several access levels uses to restrict access to the file operation such as delete, read, or write. Tokens are used to control the access to the secure files.
- When a secure file is created, four different tokens (32-bit numbers) are generated. Each token provides a different access level to the file.
Token Type | Description |
---|---|
Master Token | Enables full access to the file. A secure file can be deleted only with the master token. The master token is unchanged as long as the file exists |
Read/Write Token | Enables read and write access. This token is generated automatically when the file has been changed |
Write Only Token | Enable write access only. This access level is can be used to protect private keys. This token is generated automatically when the file has changed |
Read Only Token | Enables read access only. This token is generated automatically when the file has changed |
Origin Authenticity
- The authentication method used for this process is a well-known methodology—PKCS#1 SHA-1 with RSA encryption (the key size is 128 or 256); creating a file signature verifies both the file integrity and the authentication of the file
- To create a digital signature for the authentication process, the vendor must have an RSA key-pair. The certificate that holds the public key must be signed by a certificate authority
- The entire chain of trust for this certificate is verified by the device using the certificate store
- Writing the service-pack and the certificate store files requires a signature; the signatures for those files are supplied by TI. For any other secure signed files, no certificate is required
Recovery Mechanism
- The SimpleLink device has embedded an internal recovery mechanism that allows the file system to be rolled back to a pre-defined image programmed in the production line
- The recovery image is a system file kept on the serial flash. The file is protected and secure. The recovery image file can be viewed by the file list but cannot be accessed from the host
- The process of restore to factory is fail-safe, as it resumes even if there was a power failure during the operation
System Alerts
- The SimpleLink device provides a data tampering procedure with a security alert counter
- This procedure detects any integrity violations of the file system data, content of secure-authenticate files, and system files. It also detects unauthorized operations, such as trying to read a secure file with an invalid token
- When the system reaches the security alerts threshold, the device is locked. In addition, the host receives an asynchronous event regarding the lock
- The counter-alert lock mechanism protects against brute-force attacks
Levels of File Protection
The file system is the major building block for most of the device’s security and stores the device’s sensitive information (IP, system configuration, user-files, etc.). The file system maintains the integrity and authenticity of this data, and files on the external storage can be classified as follows:
- Non-secure Files:
- Unrestricted read and write access to this plain text file type
- No validation is performed
- Secure Files:
- File content is stored as AES128 encrypted text and the encryption key is changed on every write
- Access control where the file can only be accessed with a valid token
- Cloning protection since a file cannot be copied to other devices
- Secure-n-authenticated Files:
- Contains all security properties of secure files
- The authenticity of the writer of the secure-n-authenticated file is validated on every write cycle
- The authenticity uses digital signature of the file content, the file is signed using an RSA private key, and supports X.509 certificate chain
- On every open for read operation the file content is verified, by calculating the file SHA and comparing it to a signature which was created and kept during the write-cycle
- The device maintains a root certificate on ROM, and the chain-of-trust must be rooted to the device’s ROM either:
- By using a chain with TI’s ROM certificate
- Or, by using a chain whose root certificate is part of the device’s "Certificate Store" file. The "Certificate Store" file is stored on the file system and is signed by the ROM certificate
Secure File System API
The 4 primary API calls for read, write, open, and close used in the secure file system demos are explored below:
sl_FsOpen:
_i32 sl_FsOpen(const _u8 *pFileName,const _u32 AccessModeAndMaxSize,_u32 *pToken);
- Function Parameters:
pFileName
: File name ,pointer to a null-terminated string contains file namepToken
: Uses as input token in case of open-read and in case of open-write with vendor flag, otherwise it is output tokenAccessModeAndMaxSize
:SL_FS_READ
: Read a fileSL_FS_WRITE
: Open for write for an existing fileSL_FS_OVERWRITE
: In case the file do exists open it for write else create the file and open for writeSL_FS_CREATE
: Create a new file and open for write
- Return Value:
- On success, file handle is returned. On error, negative error code is returned
- Function Parameters:
sl_FsRead:
_i32 sl_FsRead(const _i32 FileHdl,_u32 Offset ,_u8* pData,_u32 Len);
- Function Parameters:
FileHdl
: Handle to the file (assigned fromsl_FsOpen()
)Offset
: Offset to specific location to be read/writepData
: Pointer the receive/transmit data to the storage deviceLen
: Length to read/write
- Return Value:
- On success, returns the number of read bytes. On error, negative number is returned
- Function Parameters:
sl_FsWrite:
_i32 sl_FsWrite(const _i32 FileHdl,_u32 Offset,_u8* pData,_u32 Len);
- Function Parameters:
FileHdl
: Handle to the file (assigned fromsl_FsOpen()
)Offset
: Offset to specific location to be read/writepData
: Pointer the receive/transmit data to the storage deviceLen
: Length to read/write
- Return Value:
- On success, returns the number of written bytes. On error, negative error code is returned
- Function Parameters:
sl_FsClose:
_i32 sl_FsClose(const _i32 FileHdl,const _u8* pCeritificateFileName,const _u8* pSignature,const _u32 SignatureLen);
- Function Parameters:
FileHdl
: Handle to the file (assigned from sl_FsOpen)pCeritificateFileName
: Certificate file, or NULL if irrelevantpSignature
: The signature is SHA-1, the certificate chain may include SHA-256SignatureLen
: The actual signature length
- Return Value:
- Zero on success, or a negative value if an error occurred
- Notes:
- Close file in storage device (authentication is performed if needed)
- In case the file was opened as not secure file or as secure-not signed, any certificate or signature provided are ignored, those fields should be set to NULL
- Function Parameters:
Task 1: Preparing the Environment
The following section will discuss the steps needed to experience the file system on the CC32xx LaunchPad. This example is based on the Portable example found within the CC32xx SDK. We will enable Wi-Fi using the SimpleLink Wi-Fi APIs and the file system capabilities on top of the existing example.
Modifying the Portable Example
- In CCS, open the TI Resource Explorer (View → Resource Explorer)
Expand the folders as shown to select the Portable example, then click the Import to IDE icon at the top-right
- Be sure you select your desired project "flavor" (CC3235S-LAUNCHXL, CC3220SF-LAUNCHXL, TI-RTOS, Free-RTOS, CCS, GCC, etc.).
We will be using the TI-RTOS CCS example for this lab:
portable_CC3220SF_LAUNCHXL_tirtos_ccs
We will be using the Portable example as a base project, but we will remove the original temperature and console threads and replace them with a file-system implementation. We first have to edit the
main_tirtos.c
file. Select the code below and use to replace the entire existingmain_tirtos.c
code./* * Copyright (c) 2016, Texas Instruments Incorporated * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Texas Instruments Incorporated nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * ======== main_tirtos.c ======== */ #include <stdint.h> /* POSIX Header files */ #include <pthread.h> /* RTOS header files */ #include <ti/sysbios/BIOS.h> /* Driver header files */ #include <ti/drivers/GPIO.h> /* Example/Board Header files */ #include "Board.h" extern void *secureFSExampleThread(void *arg0); /* Stack size in bytes */ #define THREADSTACKSIZE 2048 /* * ======== main ======== */ int main(void) { pthread_t thread; pthread_attr_t pAttrs; struct sched_param priParam; int retc; int detachState; /* Call driver init functions */ Board_initGeneral(); /* Set priority and stack size attributes */ pthread_attr_init(&pAttrs); priParam.sched_priority = 1; detachState = PTHREAD_CREATE_DETACHED; retc = pthread_attr_setdetachstate(&pAttrs, detachState); if (retc != 0) { /* pthread_attr_setdetachstate() failed */ while (1); } pthread_attr_setschedparam(&pAttrs, &priParam); retc |= pthread_attr_setstacksize(&pAttrs, THREADSTACKSIZE); if (retc != 0) { /* pthread_attr_setstacksize() failed */ while (1); } retc = pthread_create(&thread, &pAttrs, secureFSExampleThread, NULL); if (retc != 0) { /* pthread_create() failed */ while (1); } /* Start the TI-RTOS scheduler */ BIOS_start(); return (0); }
main_tirtos.c
You may exclude the
temperature.c
andconsole.c
from the projet compilation.Since we are going to use the terminal in this example, we will add the common UART services. From another Wi-Fi application, copy
uart_term.c
anduart_term.h
and add the files into your project. You can find these files in the Network Terminal example:simplelink_cc32xx_sdk_x_xx_xx_xx/examples/rtos/CC3220SF_LAUNCHXL/demos/network_terminal/
. This will enable us to initialize the UART to a standard configuration (baudrate of 115200) and use a formatted output (Report()
).Create a new file titled
secure_fs.c
in your project with the following code. This will implement the new file-system example thread./* * Copyright (C) 2016 Texas Instruments Incorporated * * All rights reserved. Property of Texas Instruments Incorporated. * Restricted rights to use, duplicate or disclose this code are * granted through contract. * * The program may not be used without the written permission of * Texas Instruments Incorporated or against the terms and conditions * stipulated in the agreement under which this program has been supplied, * and under no circumstances can it be used with non-TI connectivity device. * */ #include <stdint.h> #include <stddef.h> #include <unistd.h> #include <stdarg.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <ti/drivers/net/wifi/simplelink.h> #include <pthread.h> #include "Board.h" #include "uart_term.h" /* Put callbacks and helper functions here*/ /* This is the example thread */ void *secureFSExampleThread(void *arg0) { /* Put wifi_init and sl_Start here */ /* Put Task 3 code here */ /* This is the empty while loop. This should always be at the end of the function. */ while (1); }
secure_fs.c
The modified project can be built (although it will do nothing if run at this stage). The modified project content is shown below:
Enabling SimpleLink Wi-Fi
In order to use the flash file system, we first need to enable the network processor.
Add the SimpleLink Wi-Fi host driver library
simplelink.a
to the project.- Right-click on the Portable project name and select Properties -> Build -> ARM Linker -> File Search Path
Click the document icon with the green plus next to "Include library file..."
- Browse to your SimpleLink CC32xx SDK installation, then
source/ti/drivers/net/wifi/ccs/rtos/
and selectsimplelink.a
. Select OK.
The library requires that all the SimpleLink driver callbacks must be implemented (although they are not all used by this example). Add the following code to
secure_fs.c
:void SimpleLinkWlanEventHandler(SlWlanEvent_t *pWlanEvent) { UART_PRINT("pWlanEvent->Id=%d\n\r", pWlanEvent->Id); } void SimpleLinkFatalErrorEventHandler(SlDeviceFatal_t *slFatalErrorEvent) { UART_PRINT("slFatalErrorEvent->Id=%d\n\r", slFatalErrorEvent->Id); } void SimpleLinkGeneralEventHandler(SlDeviceEvent_t *pDevEvent) { UART_PRINT("pDevEvent->Id=%d\n\r", pDevEvent->Id); } void SimpleLinkNetAppEventHandler(SlNetAppEvent_t *pNetAppEvent) { /* Unused in this application */ } void SimpleLinkSockEventHandler(SlSockEvent_t *pSock) { /* Unused in this application */ } void SimpleLinkHttpServerEventHandler(SlNetAppHttpServerEvent_t *pHttpEvent, SlNetAppHttpServerResponse_t *pHttpResponse) { /* Unused in this application */ } void SimpleLinkNetAppRequestMemFreeEventHandler (uint8_t *buffer) { /* Unused in this application */ } void SimpleLinkNetAppRequestEventHandler (SlNetAppRequest_t *pNetAppRequest, SlNetAppResponse_t *pNetAppResponse) { /* Unused in this application */ }
secure_fs.c
The following code is used to enable the SPI interface and trigger the Wi-Fi driver thread. Copy and paste this code into
secure_fs.c
above the thread function:#define SPAWN_TASK_PRIORITY (9) #define TASK_STACK_SIZE (2048) int wifi_init() { int RetVal; pthread_attr_t pAttrs_spawn; pthread_t g_spawn_thread = (pthread_t)NULL; struct sched_param priParam; GPIO_init(); SPI_init(); //create the sl_Task pthread_attr_init(&pAttrs_spawn); priParam.sched_priority = SPAWN_TASK_PRIORITY; RetVal = pthread_attr_setschedparam(&pAttrs_spawn, &priParam); RetVal |= pthread_attr_setstacksize(&pAttrs_spawn, TASK_STACK_SIZE); if(RetVal == 0) RetVal = pthread_create(&g_spawn_thread, &pAttrs_spawn, sl_Task, NULL); return RetVal; }
secure_fs.c
Call the
wifi_init()
function from the application main threadsecureFSExampleThread()
that we added earlier tosecure_fs.c
. Then we will callsl_Start()
to start the network processor. Copy and paste the following code insidesecureFSExampleThread()
before the while loop:int RetVal; /* Terminal Initialization */ InitTerm(); /* Wifi Enabling */ RetVal = wifi_init(); if(RetVal < 0) { UART_PRINT("wifi_init error: %d\n\r", RetVal); return NULL; } /* Powerup the CC3x20 Network Processor */ RetVal = sl_Start(0, 0, 0); if(RetVal < 0) { UART_PRINT("sl_Start error: %d\n\r", RetVal); return NULL; } // Device is awake (the exact role is not relevant for this example) // and ready for processing FS commands
secure_fs.c :: secureFSExampleThread()
Task 2: Retrieving file system information
Enter the following code into
secure_fs.c
to present the storage information and list of files in a readable format.static _i32 st_ShowStorageInfo() { _i32 RetVal = 0; _i32 size; _i32 used; _i32 avail; SlFsControlGetStorageInfoResponse_t storageInfo; UART_PRINT("\n\rGet Storage Info:\n\r"); RetVal = sl_FsCtl(( SlFsCtl_e)SL_FS_CTL_GET_STORAGE_INFO, 0, NULL , NULL , 0, (_u8 *)&storageInfo, sizeof(SlFsControlGetStorageInfoResponse_t), NULL ); if(RetVal < 0) { UART_PRINT("sl_FsCtl error: %d\n\r"); } size = (storageInfo.DeviceUsage.DeviceBlocksCapacity * storageInfo.DeviceUsage.DeviceBlockSize) / 1024; UART_PRINT("Total space: %dK\n\r\n\r", size); UART_PRINT("Filestsyem Size \tUsed \tAvail\t\n\r"); size = ((storageInfo.DeviceUsage.NumOfAvailableBlocksForUserFiles + storageInfo.DeviceUsage.NumOfAllocatedBlocks) * storageInfo.DeviceUsage.DeviceBlockSize) / 1024; used = (storageInfo.DeviceUsage.NumOfAllocatedBlocks * storageInfo.DeviceUsage.DeviceBlockSize) / 1024; avail = (storageInfo.DeviceUsage.NumOfAvailableBlocksForUserFiles * storageInfo.DeviceUsage.DeviceBlockSize) / 1024; UART_PRINT("%-15s %dK \t%dK \t%dK \t\n\r", "User", size, used, avail); size = (storageInfo.DeviceUsage.NumOfReservedBlocksForSystemfiles * storageInfo.DeviceUsage.DeviceBlockSize) / 1024; UART_PRINT("%-15s %dK \n\r", "System", size); size = (storageInfo.DeviceUsage.NumOfReservedBlocks * storageInfo.DeviceUsage.DeviceBlockSize) / 1024; UART_PRINT("%-15s %dK \n\r", "Reserved", size); UART_PRINT("\n\r"); UART_PRINT("\n\r"); UART_PRINT("%-32s: %d \n\r", "Max number of files", storageInfo.FilesUsage.MaxFsFiles); UART_PRINT("%-32s: %d \n\r", "Max number of system files", storageInfo.FilesUsage.MaxFsFilesReservedForSysFiles); UART_PRINT("%-32s: %d \n\r", "Number of user files", storageInfo.FilesUsage.ActualNumOfUserFiles); UART_PRINT("%-32s: %d \n\r", "Number of system files", storageInfo.FilesUsage.ActualNumOfSysFiles); UART_PRINT("%-32s: %d \n\r", "Number of alert", storageInfo.FilesUsage.NumOfAlerts); UART_PRINT("%-32s: %d \n\r", "Number Alert threshold", storageInfo.FilesUsage.NumOfAlertsThreshold); UART_PRINT("%-32s: %d \n\r", "FAT write counter", storageInfo.FilesUsage.FATWriteCounter); UART_PRINT("%-32s: ", "Bundle state"); if(storageInfo.FilesUsage.Bundlestate == SL_FS_BUNDLE_STATE_STOPPED) { UART_PRINT("%s \n\r", "Stopped"); } else if(storageInfo.FilesUsage.Bundlestate == SL_FS_BUNDLE_STATE_STARTED) { UART_PRINT("%s \n\r", "Started"); } else if(storageInfo.FilesUsage.Bundlestate == SL_FS_BUNDLE_STATE_PENDING_COMMIT) { UART_PRINT("%s \n\r", "Commit pending"); } UART_PRINT("\n\r"); return RetVal; } #define MAX_FILE_ENTRIES 4 typedef struct { SlFileAttributes_t attribute; char fileName[SL_FS_MAX_FILE_NAME_LENGTH]; } slGetfileList_t; static _i32 st_listFiles(_i32 numOfFiles, int bPrintDescription) { int retVal = SL_ERROR_BSD_ENOMEM; _i32 index = -1; _i32 fileCount = 0; slGetfileList_t *buffer = malloc(MAX_FILE_ENTRIES * sizeof(slGetfileList_t)); UART_PRINT("\n\rRead files list:\n\r"); if(buffer) { while( numOfFiles > 0 ) { _i32 i; _i32 numOfEntries = (numOfFiles < MAX_FILE_ENTRIES) ? numOfFiles : MAX_FILE_ENTRIES; // Get FS list retVal = sl_FsGetFileList(&index, numOfEntries, sizeof(slGetfileList_t), (_u8*)buffer, SL_FS_GET_FILE_ATTRIBUTES); if(retVal < 0) { UART_PRINT("sl_FsGetFileList error: %d\n\r", retVal); break; } if(retVal == 0) { break; } // Print single column format for (i = 0; i < retVal; i++) { UART_PRINT("[%3d] ", ++fileCount); UART_PRINT("%-40s\t", buffer[i].fileName); UART_PRINT("%8d\t", buffer[i].attribute.FileMaxSize); UART_PRINT("0x%03x\t", buffer[i].attribute.Properties); UART_PRINT("\n\r"); } numOfFiles -= retVal; } UART_PRINT("\n\r"); if(bPrintDescription) { UART_PRINT(" File properties flags description:\n\r"); UART_PRINT(" 0x001 - Open file commit\n\r"); UART_PRINT(" 0x002 - Open bundle commit\n\r"); UART_PRINT(" 0x004 - Pending file commit\n\r"); UART_PRINT(" 0x008 - Pending bundle commit\n\r"); UART_PRINT(" 0x010 - Secure file\n\r"); UART_PRINT(" 0x020 - No file safe\n\r"); UART_PRINT(" 0x040 - System file\n\r"); UART_PRINT(" 0x080 - System with user access\n\r"); UART_PRINT(" 0x100 - No valid copy\n\r"); UART_PRINT(" 0x200 - Public write\n\r"); UART_PRINT(" 0x400 - Public read\n\r"); UART_PRINT("\n\r"); } free (buffer); } return retVal; }
secure_fs.c
Add the function calls to the example main thread (after
wifi_init()
andsl_Start()
):st_ShowStorageInfo(); st_listFiles(40, 1);
secure_fs.c :: secureFSExampleThread()
Open a terminal emulation program, select the UART serial port, and set the baud rate to 115200. Please see the Wi-Fi Fundamentals lab if you are not familiar with how to set up your terminal.
Build, load, and run the example code. The following shows an example terminal output.
Task 3: Demonstrating the Fail-Safe (Recovery) mechanism
The next task creates an unsecure fail-safe file and then tries to override it. The example will first demonstrate a rollback (usually due to an error in the file validation) and then show a succesfull commit.
First, we will add simple "write file" and "read file" helper functions. These methods will handle fragmentation during reading from (or writing to) the network processor. Copy the following code into
secure_fs.c
.static int st_writeFile( _i32 fileHandle, _u32 length, _const char *buffer) { int RetVal; _i32 offset = 0; while(offset < length) { // write data to open file RetVal = sl_FsWrite(fileHandle, offset, (_u8 *)buffer, length); if (RetVal <= 0) { UART_PRINT("sl_FsWrite error: %d\n\r" ,RetVal); return RetVal; } else { offset += RetVal; length -= RetVal; } } UART_PRINT("Wrote %d bytes...\n\r", offset); return offset; } static int st_readFile( _i32 fileHandle, _u32 length) { int offset = 0; int RetVal = 0; char buff[100]; while(offset < length) { RetVal = sl_FsRead(fileHandle, offset, (_u8*)buff, length); if(RetVal == SL_ERROR_FS_OFFSET_OUT_OF_RANGE) {// EOF break; } if(RetVal < 0) {// Error UART_PRINT("sl_FsRead error: %d\n\r", RetVal); return RetVal; } offset += RetVal; length -= RetVal; } UART_PRINT("Read \"%s\" (%d bytes)...\n\r", buff, offset); return offset; }
secure_fs.c
We will add content for the new file and the content that will overide it.
#define MAX_FILE_SIZE 100 #define TEST_FILENAME "NewFile" #define TEST_CERTIFICATE "dummy-trusted-cert" _const char originalContent[] = "This is the original content!"; _const char secondaryContent[] = "Overridden by the secondary content...";
secure_fs.c
Code for Task 3
All code in the rest of this task should be copied-and-pasted in the given order inside
secureFSExampleThread()
before the while(1) loop.Now, let's create an unsecured file with the fail-safe option in the
secureFSExampleThread()
.// Creating unsecured, MAX_SIZE file (this is the maximum length allowed) _i32 fd = sl_FsOpen(TEST_FILENAME, (SL_FS_CREATE | SL_FS_CREATE_FAILSAFE | SL_FS_CREATE_MAX_SIZE(MAX_FILE_SIZE)), 0); if(fd < 0) { UART_PRINT("sl_FsOpen error: %d\n\r", fd); } else { st_writeFile(fd, strlen(originalContent), originalContent); RetVal = sl_FsClose(fd, 0, 0, 0); if(fd < 0) { UART_PRINT("sl_FsClose error: %d\n\r", RetVal); } }
secure_fs.c :: secureFSExampleThread()
Then we can add some code to verify the successful file creation within the example thread.
// Check that the new file exists (make sure the "0x020 - No File Safe" property is disabled for the new file) // Note that the size is rounded to the nearest block size st_listFiles(40,0); // Read the file content UART_PRINT("Reading file after CREATION:\n\r"); fd = sl_FsOpen(TEST_FILENAME, SL_FS_READ, 0); if(fd < 0) { UART_PRINT("sl_FsOpen error: %d\n\r", fd); } else { st_readFile(fd, MAX_FILE_SIZE); RetVal = sl_FsClose(fd, 0, 0, 0); if(fd < 0) { UART_PRINT("sl_FsClose error: %d\n\r", RetVal); } }
secure_fs :: secureFSExampleThread()
Terminal output for the creation of a new file
Replacing the file content
During OTA or in normal operation, a developer may want to replace the content of a file. The file-safe option (used in the previous task) provides a mechanism that allows you to revert to the original file in case the new content is damaged. The new content is kept as a candidate while the old copy is still in flash. After the new file has been tested, it can be committed (i.e. the old content will be deleted) or it can be rolled back so the old copy becomes the valid one. This feature can be enabled for a specific file or to a group of files (each should be created with the
SL_FS_WRITE_BUNDLE_FILE
). In this case, the commit or rollback will affect the entire bundle.Assuming the
NewFile
is already in flash, we will try to override it. Note that theSL_FS_WRITE_MUST_COMMIT
should be added to the File Open flags.UART_PRINT("\n\r\n\r ** Replacing file content\n\r"); fd = sl_FsOpen(TEST_FILENAME, SL_FS_WRITE | SL_FS_WRITE_MUST_COMMIT, 0); if(fd < 0) { UART_PRINT("sl_FsOpen (re-write) error: %d\n\r", fd); } else { st_writeFile(fd, strlen(secondaryContent), secondaryContent); RetVal = sl_FsClose(fd, 0, 0, 0); if(RetVal < 0) { UART_PRINT("sl_FsClose (re-write) error: %d\n\r", RetVal); } }
secure_fs.c :: secureFSExampleThread()
In order to verify that the file was re-written, we will read its content again by reusing the code from step 4.
// Read the file content UART_PRINT("Reading File before ROLLBACK:\n\r"); fd = sl_FsOpen(TEST_FILENAME, SL_FS_READ, 0); if(fd < 0) { UART_PRINT("sl_FsOpen error: %d\n\r", fd); } else { st_readFile(fd, MAX_FILE_SIZE); RetVal = sl_FsClose(fd, 0, 0, 0); if(fd < 0) { UART_PRINT("sl_FsClose error: %d\n\r", RetVal); } }
secure_fs.c :: secureFSExampleThread()
After the file is re-written, we can run some sanity tests and act accordingly. We'll first follow a failure case which will trigger a rollback command. Note that in case the MCU image is replaced, it will rollback automatically upon the next MCU reset if
COMMIT
is not used.SlFsControl_t FsControl; RetVal = sl_FsCtl(SL_FS_CTL_ROLLBACK, 0, TEST_FILENAME, (_u8 *)&FsControl, sizeof(SlFsControl_t), NULL, 0, 0);
secure_fs.c :: secureFSExampleThread()
If we check the file again (repeating step 4), the original content should appear.
// Read the file content UART_PRINT("Reading File after ROLLBACK:\n\r"); fd = sl_FsOpen(TEST_FILENAME, SL_FS_READ, 0); if(fd < 0) { UART_PRINT("sl_FsOpen error: %d\n\r", fd); } else { st_readFile(fd, MAX_FILE_SIZE); RetVal = sl_FsClose(fd, 0, 0, 0); if(fd < 0) { UART_PRINT("sl_FsClose error: %d\n\r", RetVal); } }
secure_fs.c :: secureFSExampleThread()
To test a successful file replacement, repeat steps 5-7, but use
SL_FS_CTL_COMMIT
in step 7 (instead of theSL_FS_CTL_ROLLBACK
).UART_PRINT("\n\r\n\r ** Replacing file content (again)\n\r"); fd = sl_FsOpen(TEST_FILENAME, SL_FS_WRITE | SL_FS_WRITE_MUST_COMMIT, 0); if(fd < 0) { UART_PRINT("sl_FsOpen (re-write) error: %d\n\r", fd); } else { st_writeFile(fd, strlen(secondaryContent), secondaryContent); RetVal = sl_FsClose(fd, 0, 0, 0); if(RetVal < 0) { UART_PRINT("sl_FsClose (re-write) error: %d\n\r", RetVal); } } // Read the file content UART_PRINT("Reading File before COMMIT:\n\r"); fd = sl_FsOpen(TEST_FILENAME, SL_FS_READ, 0); if(fd < 0) { UART_PRINT("sl_FsOpen error: %d\n\r", fd); } else { st_readFile(fd, MAX_FILE_SIZE); RetVal = sl_FsClose(fd, 0, 0, 0); if(fd < 0) { UART_PRINT("sl_FsClose error: %d\n\r", RetVal); } } RetVal = sl_FsCtl(SL_FS_CTL_COMMIT, 0, TEST_FILENAME, (_u8 *)&FsControl, sizeof(SlFsControl_t), NULL, 0, 0);
secure_fs.c :: secureFSExampleThread()
If we check the file again (repeating step 4), now the secondary content should appear.
// Read the file content UART_PRINT("Reading file after COMMIT:\n\r"); fd = sl_FsOpen(TEST_FILENAME, SL_FS_READ, 0); if(fd < 0) { UART_PRINT("sl_FsOpen error: %d\n\r", fd); } else { st_readFile(fd, MAX_FILE_SIZE); RetVal = sl_FsClose(fd, 0, 0, 0); if(fd < 0) { UART_PRINT("sl_FsClose error: %d\n\r", RetVal); } }
secure_fs.c :: secureFSExampleThread()
To complete this task, we will delete the file from the file system and verify.
UART_PRINT("\n\r\n\r ** Deleting the file\n\r"); RetVal = sl_FsDel(TEST_FILENAME, 0); st_listFiles(40,0); UART_PRINT("Example complete\n\r");
secure_fs.c :: secureFSExampleThread()
Build and run the example. Verify the results using the debug terminal.
Note that the following code example uses some additional helper methods to simplify the implementation.
/*
* Copyright (C) 2016 Texas Instruments Incorporated
*
* All rights reserved. Property of Texas Instruments Incorporated.
* Restricted rights to use, duplicate or disclose this code are
* granted through contract.
*
* The program may not be used without the written permission of
* Texas Instruments Incorporated or against the terms and conditions
* stipulated in the agreement under which this program has been supplied,
* and under no circumstances can it be used with non-TI connectivity device.
*
*/
#include <stdint.h>
#include <stddef.h>
#include <unistd.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ti/drivers/net/wifi/simplelink.h>
#include <pthread.h>
#include "Board.h"
#include "uart_term.h"
#define SPAWN_TASK_PRIORITY (9)
#define TASK_STACK_SIZE (2048)
#define MAX_FILE_SIZE 100
#define TEST_FILENAME "NewFile"
#define TEST_CERTIFICATE "dummy-trusted-cert"
#define MAX_FILE_ENTRIES 4
typedef struct
{
SlFileAttributes_t attribute;
char fileName[SL_FS_MAX_FILE_NAME_LENGTH];
} slGetfileList_t;
_const char originalContent[] = "This is the original content!";
_const char secondaryContent[] = "Overridden by the secondary content...";
/************************************************************************
* Static Functions
*************************************************************************/
void SimpleLinkWlanEventHandler(SlWlanEvent_t *pWlanEvent)
{
UART_PRINT("[WLAN EVENT] pWlanEvent->Id=%d\n\r", pWlanEvent->Id);
}
void SimpleLinkFatalErrorEventHandler(SlDeviceFatal_t *slFatalErrorEvent)
{
UART_PRINT("[FATAL ERROR EVENT] slFatalErrorEvent->Id=%d\n\r", slFatalErrorEvent->Id);
}
void SimpleLinkNetAppEventHandler(SlNetAppEvent_t *pNetAppEvent)
{
SlNetAppEventData_u *pNetAppEventData = NULL;
if(NULL == pNetAppEvent) return;
pNetAppEventData = &pNetAppEvent->Data;
switch(pNetAppEvent->Id)
{
case SL_NETAPP_EVENT_IPV4_ACQUIRED:
{
UART_PRINT("[NETAPP EVENT] IPv4 acquired: IP = %d.%d.%d.%d\n\r",
(uint8_t)SL_IPV4_BYTE(pNetAppEventData->IpAcquiredV4.Ip,3),
(uint8_t)SL_IPV4_BYTE(pNetAppEventData->IpAcquiredV4.Ip,2),
(uint8_t)SL_IPV4_BYTE(pNetAppEventData->IpAcquiredV4.Ip,1),
(uint8_t)SL_IPV4_BYTE(pNetAppEventData->IpAcquiredV4.Ip,0));
UART_PRINT(" Gateway = %d.%d.%d.%d\n\r",
(uint8_t)SL_IPV4_BYTE(pNetAppEventData->IpAcquiredV4.Gateway,3),
(uint8_t)SL_IPV4_BYTE(pNetAppEventData->IpAcquiredV4.Gateway,2),
(uint8_t)SL_IPV4_BYTE(pNetAppEventData->IpAcquiredV4.Gateway,1),
(uint8_t)SL_IPV4_BYTE(pNetAppEventData->IpAcquiredV4.Gateway,0));
}
break;
case SL_NETAPP_EVENT_IPV4_LOST:
case SL_NETAPP_EVENT_DHCP_IPV4_ACQUIRE_TIMEOUT:
{
UART_PRINT("[NETAPP EVENT] IPv4 lost Id or timeout, Id [0x%x]!!!\n\r", pNetAppEvent->Id);
//SignalEvent(APP_EVENT_DISCONNECT); /* use existing disconnect event, no need for another event */
}
break;
case SL_NETAPP_EVENT_IPV6_ACQUIRED:
UART_PRINT("[NETAPP EVENT] IPv6 acquired by the device\n\r");
break;
case SL_NETAPP_EVENT_DHCPV4_LEASED:
UART_PRINT("[NETAPP EVENT] IP Leased to Client\n\r");
break;
case SL_NETAPP_EVENT_DHCPV4_RELEASED:
UART_PRINT("[NETAPP EVENT] IP Released to Client\n\r");
break;
default:
{
UART_PRINT("[NETAPP EVENT] Unexpected event with Id [0x%x] \n\r", pNetAppEvent->Id);
}
break;
}
}
void SimpleLinkHttpServerEventHandler(SlNetAppHttpServerEvent_t *pHttpEvent,
SlNetAppHttpServerResponse_t *pHttpResponse)
{
UART_PRINT("[HTTP SERVER EVENT] Unexpected HTTP server event \n\r");
}
void SimpleLinkGeneralEventHandler(SlDeviceEvent_t *pDevEvent)
{
UART_PRINT("[GENERAL EVENT] pDevEvent->Id=%d\n\r", pDevEvent->Id);
}
void SimpleLinkSockEventHandler(SlSockEvent_t *pSock)
{
UART_PRINT("[SOCKET EVENT] pSock->Event=%d\n\r", pSock->Event);
}
void SimpleLinkNetAppRequestMemFreeEventHandler (uint8_t *buffer)
{
/* Unused in this application */
}
void SimpleLinkNetAppRequestEventHandler (SlNetAppRequest_t *pNetAppRequest, SlNetAppResponse_t *pNetAppResponse)
{
/* Unused in this application */
}
/* st_wifiInit -
* enables the inetrface to the CC3X20 Network Processor
*/
int st_wifiInit()
{
int RetVal;
pthread_attr_t pAttrs_spawn;
pthread_t g_spawn_thread = (pthread_t)NULL;
struct sched_param priParam;
GPIO_init();
SPI_init();
//create the sl_Task
pthread_attr_init(&pAttrs_spawn);
priParam.sched_priority = SPAWN_TASK_PRIORITY;
RetVal = pthread_attr_setschedparam(&pAttrs_spawn, &priParam);
RetVal |= pthread_attr_setstacksize(&pAttrs_spawn, TASK_STACK_SIZE);
if(RetVal == 0)
RetVal = pthread_create(&g_spawn_thread, &pAttrs_spawn, sl_Task, NULL);
return RetVal;
}
/* st_showStorageInfo -
* Retrieves and displays the storage information
*/
static _i32 st_showStorageInfo()
{
_i32 RetVal = 0;
_i32 size;
_i32 used;
_i32 avail;
SlFsControlGetStorageInfoResponse_t storageInfo;
UART_PRINT("\n\rGet Storage Info:\n\r");
RetVal = sl_FsCtl(( SlFsCtl_e)SL_FS_CTL_GET_STORAGE_INFO,
0,
NULL ,
NULL ,
0,
(_u8 *)&storageInfo,
sizeof(SlFsControlGetStorageInfoResponse_t),
NULL );
if(RetVal < 0)
{
UART_PRINT("sl_FsCtl error: %d\n\r");
}
size = (storageInfo.DeviceUsage.DeviceBlocksCapacity *
storageInfo.DeviceUsage.DeviceBlockSize) / 1024;
UART_PRINT("Total space: %dK\n\r\n\r", size);
UART_PRINT("Filestsyem Size \tUsed \tAvail\t\n\r");
size = ((storageInfo.DeviceUsage.NumOfAvailableBlocksForUserFiles +
storageInfo.DeviceUsage.NumOfAllocatedBlocks) *
storageInfo.DeviceUsage.DeviceBlockSize) / 1024;
used = (storageInfo.DeviceUsage.NumOfAllocatedBlocks *
storageInfo.DeviceUsage.DeviceBlockSize) / 1024;
avail = (storageInfo.DeviceUsage.NumOfAvailableBlocksForUserFiles *
storageInfo.DeviceUsage.DeviceBlockSize) / 1024;
UART_PRINT("%-15s %dK \t%dK \t%dK \t\n\r", "User", size, used, avail);
size = (storageInfo.DeviceUsage.NumOfReservedBlocksForSystemfiles *
storageInfo.DeviceUsage.DeviceBlockSize) / 1024;
UART_PRINT("%-15s %dK \n\r", "System", size);
size = (storageInfo.DeviceUsage.NumOfReservedBlocks *
storageInfo.DeviceUsage.DeviceBlockSize) / 1024;
UART_PRINT("%-15s %dK \n\r", "Reserved", size);
UART_PRINT("\n\r");
UART_PRINT("\n\r");
UART_PRINT("%-32s: %d \n\r", "Max number of files",
storageInfo.FilesUsage.MaxFsFiles);
UART_PRINT("%-32s: %d \n\r", "Max number of system files",
storageInfo.FilesUsage.MaxFsFilesReservedForSysFiles);
UART_PRINT("%-32s: %d \n\r", "Number of user files",
storageInfo.FilesUsage.ActualNumOfUserFiles);
UART_PRINT("%-32s: %d \n\r", "Number of system files",
storageInfo.FilesUsage.ActualNumOfSysFiles);
UART_PRINT("%-32s: %d \n\r", "Number of alert",
storageInfo.FilesUsage.NumOfAlerts);
UART_PRINT("%-32s: %d \n\r", "Number Alert threshold",
storageInfo.FilesUsage.NumOfAlertsThreshold);
UART_PRINT("%-32s: %d \n\r", "FAT write counter",
storageInfo.FilesUsage.FATWriteCounter);
UART_PRINT("%-32s: ", "Bundle state");
if(storageInfo.FilesUsage.Bundlestate == SL_FS_BUNDLE_STATE_STOPPED)
{
UART_PRINT("%s \n\r", "Stopped");
}
else if(storageInfo.FilesUsage.Bundlestate == SL_FS_BUNDLE_STATE_STARTED)
{
UART_PRINT("%s \n\r", "Started");
}
else if(storageInfo.FilesUsage.Bundlestate ==
SL_FS_BUNDLE_STATE_PENDING_COMMIT)
{
UART_PRINT("%s \n\r", "Commit pending");
}
UART_PRINT("\n\r");
return RetVal;
}
/* st_listFiles -
* Retrieves and displays the list of files in the flash
*/
static _i32 st_listFiles(int bShowDescription)
{
int retVal = SL_ERROR_BSD_ENOMEM;
_i32 index = -1;
_i32 fileCount = 0;
_i32 numOfFiles = 255;
slGetfileList_t *buffer = malloc(MAX_FILE_ENTRIES * sizeof(slGetfileList_t));
UART_PRINT("\n\rRead files list:\n\r");
if(buffer)
{
while( numOfFiles > 0 )
{
_i32 i;
_i32 numOfEntries = (numOfFiles < MAX_FILE_ENTRIES) ? numOfFiles : MAX_FILE_ENTRIES;
// Get FS list
retVal = sl_FsGetFileList(&index,
numOfEntries,
sizeof(slGetfileList_t),
(_u8*)buffer,
SL_FS_GET_FILE_ATTRIBUTES);
if(retVal < 0)
{
UART_PRINT("sl_FsGetFileList error: %d\n\r", retVal);
break;
}
if(retVal == 0)
{
break;
}
// Print single column format
for (i = 0; i < retVal; i++)
{
UART_PRINT("[%3d] ", ++fileCount);
UART_PRINT("%-40s\t", buffer[i].fileName);
UART_PRINT("%8d\t", buffer[i].attribute.FileMaxSize);
UART_PRINT("0x%03x\t", buffer[i].attribute.Properties);
UART_PRINT("\n\r");
}
numOfFiles -= retVal;
}
UART_PRINT("\n\r");
if(bShowDescription)
{
UART_PRINT(" File properties flags description:\n\r");
UART_PRINT(" 0x001 - Open file commit\n\r");
UART_PRINT(" 0x002 - Open bundle commit\n\r");
UART_PRINT(" 0x004 - Pending file commit\n\r");
UART_PRINT(" 0x008 - Pending bundle commit\n\r");
UART_PRINT(" 0x010 - Secure file\n\r");
UART_PRINT(" 0x020 - No file safe\n\r");
UART_PRINT(" 0x040 - System file\n\r");
UART_PRINT(" 0x080 - System with user access\n\r");
UART_PRINT(" 0x100 - No valid copy\n\r");
UART_PRINT(" 0x200 - Public write\n\r");
UART_PRINT(" 0x400 - Public read\n\r");
UART_PRINT("\n\r");
}
free (buffer);
}
return retVal;
}
/* st_writeFile -
* Helper function for writing "length" bytes from a user buffer to a (an open) file
*/
static int st_writeFile( _i32 fileHandle, _u32 length, _const char *buffer)
{
int RetVal = 0;
_i32 offset = 0;
while(offset < length)
{
// write data to open file
RetVal = sl_FsWrite(fileHandle,
offset,
(_u8 *)buffer,
length);
if (RetVal <= 0)
{
UART_PRINT("sl_FsWrite error: %d\n\r" ,RetVal);
return RetVal;
}
else
{
offset += RetVal;
length -= RetVal;
}
}
UART_PRINT(" Wrote %d bytes...\n\r", offset);
return offset;
}
/* st_writeFile -
* Helper function for reading "length" bytes from an open file to the user buffer.
* (also prints the read content as a string).
*/
static int st_readFile( _i32 fileHandle, _u32 length, _i8 *buffer)
{
int offset = 0;
int RetVal = 0;
while(offset < length)
{
RetVal = sl_FsRead(fileHandle, offset, (_u8*)buffer, length);
if(RetVal == SL_ERROR_FS_OFFSET_OUT_OF_RANGE)
{// EOF
break;
}
if(RetVal < 0)
{// Error
UART_PRINT("sl_FsRead error: %d\n\r", RetVal);
return RetVal;
}
offset += RetVal;
length -= RetVal;
}
UART_PRINT(" Read \"%s\" (%d bytes)...\n\r", buffer, offset);
return offset;
}
/* st_openForRead -
* Open and read file content
*/
int st_openForRead(const _u8 *filename)
{
int RetVal = 0;
_i8 buffer[MAX_FILE_SIZE];
_i32 fd;
fd = sl_FsOpen(filename, SL_FS_READ, 0);
if(fd < 0)
{
UART_PRINT("sl_FsOpen (read) error: %d\n\r", fd);
}
else
{
st_readFile(fd, MAX_FILE_SIZE, buffer);
RetVal = sl_FsClose(fd, 0, 0, 0);
if(RetVal < 0)
{
UART_PRINT("sl_FsClose (read) error: %d\n\r", RetVal);
}
}
return RetVal;
}
/* st_openForWrite -
* Open (an existing file) and re-write its content
*/
int st_openForWrite(const _u8 *filename, const char *content)
{
int RetVal = 0;
_i32 fd;
// Trying to override the file (writing new content to it)
fd = sl_FsOpen(filename, SL_FS_WRITE | SL_FS_WRITE_MUST_COMMIT, 0);
if(fd < 0)
{
UART_PRINT("sl_FsOpen (re-write) error: %d\n\r", fd);
}
else
{
st_writeFile(fd, strlen(secondaryContent), content);
RetVal = sl_FsClose(fd, 0, 0, 0);
if(RetVal < 0)
{
UART_PRINT("sl_FsClose (re-write) error: %d\n\r", RetVal);
}
}
return RetVal;
}
int st_task3_createFailsafeFile()
{
int RetVal = 0;
_i32 fd;
UART_PRINT("\n\r\n\r ** Creating the fail-safe file\n\r");
/* Creating Unsecured FailSafe file */
fd = sl_FsOpen(TEST_FILENAME,
(SL_FS_CREATE | SL_FS_CREATE_FAILSAFE | SL_FS_CREATE_MAX_SIZE(MAX_FILE_SIZE)),
0);
if(fd < 0)
{
UART_PRINT("sl_FsOpen (create) error: %d\n\r", fd);
}
else
{
st_writeFile(fd, strlen(originalContent), originalContent);
RetVal = sl_FsClose(fd, 0, 0, 0);
if(RetVal < 0)
{
UART_PRINT("sl_FsClose (create) error: %d\n\r", RetVal);
}
}
/* Verifying the successful creation */
st_listFiles(0);
UART_PRINT("Reading %s after CREATION:\n\r");
st_openForRead(TEST_FILENAME);
return RetVal;
}
/* st_task3_replaceFileContent -
* demonstrates the replacing of file content and the Rollback / Commit operation
*/
int st_task3_replaceFileContent()
{
int RetVal = 0;
SlFsControl_t FsControl;
UART_PRINT("\n\r\n\r ** Replacing file content\n\r");
// Write the secondary content to the test file
st_openForWrite(TEST_FILENAME, secondaryContent);
UART_PRINT("Reading File before ROLLBACK:\n\r");
st_openForRead(TEST_FILENAME);
RetVal = sl_FsCtl(SL_FS_CTL_ROLLBACK, 0, TEST_FILENAME, (_u8 *)&FsControl, sizeof(SlFsControl_t), NULL, 0, 0);
UART_PRINT("Reading File after ROLLBACK:\n\r");
st_openForRead(TEST_FILENAME);
UART_PRINT("\n\r\n\r ** Replacing file content (again)\n\r");
// Re-write the secondary content to the test file
st_openForWrite(TEST_FILENAME, secondaryContent);
UART_PRINT("Reading File before COMMIT:\n\r");
st_openForRead(TEST_FILENAME);
RetVal = sl_FsCtl(SL_FS_CTL_COMMIT, 0, TEST_FILENAME, (_u8 *)&FsControl, sizeof(SlFsControl_t), NULL, 0, 0);
UART_PRINT("Reading File after COMMIT:\n\r");
st_openForRead(TEST_FILENAME);
return RetVal;
}
/* st_task3_deleteFile -
* demonstrates file deletion
*/
int st_task3_deleteFile()
{
UART_PRINT("\n\r\n\r ** Deleting the file\n\r");
sl_FsDel(TEST_FILENAME, 0);
st_listFiles(0);
return 0;
}
void *secureFSExampleThread(void *arg0)
{
int RetVal;
InitTerm();
RetVal = st_wifiInit();
if(RetVal < 0)
{
UART_PRINT("wifi_init error: %d\n\r", RetVal);
return NULL;
}
RetVal = sl_Start(0, 0, 0);
if(RetVal < 0)
{
UART_PRINT("sl_Start error: %d\n\r", RetVal);
return NULL;
}
// Device is awake (the exact role is not relevant for this example)
// and ready for processing FS commands
/* Task 2: demonstrates retrieving file system information */
UART_PRINT("\n\r\n\r*** TASK 2\n\r");
st_showStorageInfo();
st_listFiles(1);
/* Task 3: file safe demonstration */
UART_PRINT("\n\r\n\r*** TASK 3\n\r");
RetVal = st_task3_createFailsafeFile();
if (RetVal == 0)
{
RetVal = st_task3_replaceFileContent();
}
st_task3_deleteFile();
while(1);
}
secure_fs.c
Task 4: Using tokens to control file access
This task will build on the ones above. Tokens are used to control the access to secure (encrypted) files. We will update the example code to use tokens.
Add global variables to store the tokens and the file access mode.
/* The Tokens array defines the Master, Write-Read, Write-Only and Read-Only tokens * (see SlFsTokenId_e). */ _u32 g_tokens[4] = {0x12345678, 0, 0, 0}; /* In this example we set the master token to a vendor-specific and static value. * To use the the tokens mechanism, the file should be created as SECURED. * The NOSIGNATURE flag was added, as this task doesn't require signatures (those are demonstrated in Task 5). * If we remove the VENDOR flag, the token would be gerenated by the system and will be retrieved as output of FS API. * If we remove the STATIC flag, the value will be replaced every time we write new content to the file. */ _u32 g_accessMode = SL_FS_CREATE_SECURE | SL_FS_CREATE_NOSIGNATURE | SL_FS_CREATE_VENDOR_TOKEN | SL_FS_CREATE_STATIC_TOKEN;
secure_fs.c
Note
The access control (authorization) requires that we define the file as secured.
Auto-generated tokens
When using UniFlash ImageCreator to create new file, there is an issue when using auto-generated token as currently there is no method to retrieve the token value. The possible soultions are using
VENDOR
token or setting thePUBLIC_WRITE
flags, that allows to re-write the file (e.g. in OTA) without a token. A token will still be required for reading the file.Add the access mode and the master token (pointer) to the file creation (
sl_FsOpen()
) from Task 3./* Creating FailSafe file */ _i32 fd = sl_FsOpen(TEST_FILENAME, (SL_FS_CREATE | SL_FS_CREATE_FAILSAFE | SL_FS_CREATE_MAX_SIZE(MAX_FILE_SIZE) | g_accessMode), &g_tokens[SL_FS_TOKEN_MASTER]);
secure_fs.c :: secureFSExampleThread() - Create file
Add the master token (pointer) to the file delete (
sl_FsDel()
) from Task 3.RetVal = sl_FsDel(TEST_FILENAME, g_tokens[SL_FS_TOKEN_MASTER]);
secure_fs.c :: secureFSExampleThread() - Delete file
Build and execute the example. All the attempts to read from the file or write to it should fail with error -10365 (
SL_ERROR_FS_INVALID_TOKEN_SECURITY_ALERT
). The following is an example terminal output:Important
SimpleLink Wi-Fi devices include a Software Tamper Detection mechanism which will lock the device if the number of "invalid token" errors exceeds a certain threshold (default is 15). If you run the example several times with this failure, it will lock and reprogramming (or factory reset) will be needed.
To fix this error, you'll need to add the required token to each file opening call (for
READ
andWRITE
). You may use the master token (SL_FS_TOKEN_MASTER
) or the Read/Write specific tokens as retrieved bysl_FsGetInfo()
. Paste this helper function intosecure_fs.c
./* Call this function after the secure file is created */ int st_getFileInfo() { SlFsFileInfo_t fileInfo; int RetVal; RetVal = sl_FsGetInfo(TEST_FILENAME, g_tokens[SL_FS_TOKEN_MASTER], &fileInfo); if(0 == RetVal) { g_tokens[SL_FS_TOKEN_READ_ONLY] = fileInfo.Token[SL_FS_TOKEN_READ_ONLY]; g_tokens[SL_FS_TOKEN_WRITE_ONLY] = fileInfo.Token[SL_FS_TOKEN_WRITE_ONLY]; g_tokens[SL_FS_TOKEN_WRITE_READ] = fileInfo.Token[SL_FS_TOKEN_WRITE_READ]; } return RetVal; }
secure_fs.c
Call
st_getFileInfo()
after the secured file is created and closed in the example thread.st_getFileInfo();
secure_fs.c :: secureFSExampleThread()
and edit all
sl_FsOpen()
calls to use the relevant tokens for write and read.fd = sl_FsOpen(TEST_FILENAME, SL_FS_READ, &g_tokens[SL_FS_TOKEN_READ_ONLY]);
secure_fs.c :: secureFSExampleThread() - Reads
fd = sl_FsOpen(TEST_FILENAME, SL_FS_WRITE | SL_FS_WRITE_MUST_COMMIT, &g_tokens[SL_FS_TOKEN_WRITE_READ]);
secure_fs.c :: secureFSExampleThread() - Writes
You can also use master token, or use
WRITE_ONLY
instead ofWRITE_READ
.Add the master token to your rollback or commit configuration commands from task 3. These are how each command would look:
RetVal = sl_FsCtl(SL_FS_CTL_ROLLBACK, g_tokens[SL_FS_TOKEN_MASTER], TEST_FILENAME, (_u8 *)&FsControl, sizeof(SlFsControl_t), NULL, 0, &g_tokens[SL_FS_TOKEN_MASTER]);
secure_fs.c :: secureFSExampleThread() - ROLLBACK
RetVal = sl_FsCtl(SL_FS_CTL_COMMIT, g_tokens[SL_FS_TOKEN_MASTER], TEST_FILENAME, (_u8 *)&FsControl, sizeof(SlFsControl_t), NULL, 0, &g_tokens[SL_FS_TOKEN_MASTER]);
secure_fs.c :: secureFSExampleThread() - COMMIT
Build and execute the example. There should not be any more errors.
Task 5: Using certificates to verify authenticity and integrity
The authentication method used for this process is a well-known methodology. Creating a file signature verifies both the file integrity and the authentication of the file. To create a digital signature for the authentication process, the vendor must have an RSA key-pair. The certificate that holds the public key must be signed by a certificate authority. In this example, we will use the dummy "playground" certificate that is provided for development purposes only. The entire chain of trust for this certificate is verified by the device using the certificate store. A signature is required to write the service-pack and the certificate store files; the signatures for those files are supplied by TI. For any other secure signed files, no certificate is required. Integrity is checked when a file is opened for read, and authenticity is checked when a file closed after write.
Load the entire "playground" dummy trusted chain onto your serial flash (
dummy-trusted-cert
,dummy-trusted-ca-cert
, anddummy-root-ca-cert
).Production releases
In a production release, the secured files should be signed using a customer certificate that was signed by a known root CA (Certificate Authority). The full list of root CAs supported by TI's certificate store is available in the SDK:
simplelink_cc32xx_sdk_x_xx_xx_xx/tools/cc32xx_tools/certificate-catalog/readme.html
For more details on certificate handling, refer to the SimpleLink Wi-Fi Certificates Handling User's Guide.
Next, we will create the signature using the dummy playground private-key (see
dummy-trusted-cert-key
insimplelink_cc32xx_sdk_x_xx_xx_xx/tools/cc32xx_tools/certificate-playground/
) and openssl from a command line.Copy the contents of the
originalContent
array we created in task 3 (fromsecure_fs.c
) to a text file (e.g:origin.txt
)$ cat > origin.txt This is the original content! <Ctrl + D>
and verify using the following command to read its contents:
$ cat origin.txt This is the original content!
Use the following command to create the signature in hex format via command line.
dummy-trusted-cert-key
(found insimplelink_cc32xx_sdk_x_xx_xx_xx/tools/cc32xx_tools/certificate-playground
) must be in the same file directory you are working in.$ openssl dgst -hex -c -sha1 -keyform DER -sign dummy-trusted-cert-key -out origin.hex.sign origin.txt
The result should look like:
$ cat origin.hex.sign RSA-SHA1(origin.txt)= 28:fa:72:ea:9e:fe:d2:8c:00:72:04:d4:02:fb:35:16:31:c9:6d:d8:6d:c5:26:8e:75:e4:36:b2:f3:df:ac:1d:5a:4f:80:46:f4:1e:65:da:c0:05:bf:29:ff:79:6d:7d:a4:29:79:54:d5:76:58:57:5f:f4:f9:53:7b:48:6b:1c:7c:d9:b1:8e:df:92:c0:4e:86:0e:0a:a9:1c:76:f0:53:f1:ef:d2:4a:b9:c6:ea:b1:d8:11:df:33:4d:74:1a:be:b8:68:ae:ee:2d:5a:b3:a5:12:56:96:44:53:ea:3d:e8:5b:b8:85:75:70:b9:34:50:b0:a5:af:6a:8d:de:4e:e0:15:37:2d:e0:39:d4:fa:13:73:3a:21:9d:16:aa:f3:b8:f0:9b:76:fc:c3:7d:ee:d4:5e:3c:9c:0e:e7:de:88:0d:2f:93:e4:b0:42:e9:94:10:69:3b:4b:b6:06:0d:87:d1:74:6e:6c:f4:85:6a:f5:a3:ac:76:e7:b1:86:c3:ed:69:7a:11:71:10:26:b9:bc:22:71:1e:25:b1:c6:ec:d4:e0:61:a4:7b:26:5a:bf:70:bf:62:5e:bf:6f:dc:34:df:89:6a:87:f3:6f:37:75:2e:a9:a4:67:f6:90:97:ee:36:fb:12:5f:23:e7:98:a1:f9:4a:e2:48:dc:a1:94:6d:81:59
Convert the content of the origin.hex.sign to make it look like a C array. You may use the following commands or do it manualy:
Replace all the ':' with ', 0x'
$ sed 's/:/, 0x/g' origin.hex.sign > tmp1.c
Handle the first byte (add the '0x' prefix)
$ sed 's/\= /= {0x/' tmp1.c > tmp2.c
Fix the array name
$ sed 's/RSA-SHA1(origin.txt)/_const char originalSignature[] /' tmp2.c > origin.c
Add trailing brackets '};'
$ echo "};" >> origin.c
The result should look like:
$ cat origin.c originalSignature[] = {0x28, 0xfa, 0x72, 0xea, 0x9e, 0xfe, 0xd2, 0x8c, 0x00, 0x72, 0x04, 0xd4, 0x02, 0xfb, 0x35, 0x16, 0x31, 0xc9, 0x6d, 0xd8, 0x6d, 0xc5, 0x26, 0x8e, 0x75, 0xe4, 0x36, 0xb2, 0xf3, 0xdf, 0xac, 0x1d, 0x5a, 0x4f, 0x80, 0x46, 0xf4, 0x1e, 0x65, 0xda, 0xc0, 0x05, 0xbf, 0x29, 0xff, 0x79, 0x6d, 0x7d, 0xa4, 0x29, 0x79, 0x54, 0xd5, 0x76, 0x58, 0x57, 0x5f, 0xf4, 0xf9, 0x53, 0x7b, 0x48, 0x6b, 0x1c, 0x7c, 0xd9, 0xb1, 0x8e, 0xdf, 0x92, 0xc0, 0x4e, 0x86, 0x0e, 0x0a, 0xa9, 0x1c, 0x76, 0xf0, 0x53, 0xf1, 0xef, 0xd2, 0x4a, 0xb9, 0xc6, 0xea, 0xb1, 0xd8, 0x11, 0xdf, 0x33, 0x4d, 0x74, 0x1a, 0xbe, 0xb8, 0x68, 0xae, 0xee, 0x2d, 0x5a, 0xb3, 0xa5, 0x12, 0x56, 0x96, 0x44, 0x53, 0xea, 0x3d, 0xe8, 0x5b, 0xb8, 0x85, 0x75, 0x70, 0xb9, 0x34, 0x50, 0xb0, 0xa5, 0xaf, 0x6a, 0x8d, 0xde, 0x4e, 0xe0, 0x15, 0x37, 0x2d, 0xe0, 0x39, 0xd4, 0xfa, 0x13, 0x73, 0x3a, 0x21, 0x9d, 0x16, 0xaa, 0xf3, 0xb8, 0xf0, 0x9b, 0x76, 0xfc, 0xc3, 0x7d, 0xee, 0xd4, 0x5e, 0x3c, 0x9c, 0x0e, 0xe7, 0xde, 0x88, 0x0d, 0x2f, 0x93, 0xe4, 0xb0, 0x42, 0xe9, 0x94, 0x10, 0x69, 0x3b, 0x4b, 0xb6, 0x06, 0x0d, 0x87, 0xd1, 0x74, 0x6e, 0x6c, 0xf4, 0x85, 0x6a, 0xf5, 0xa3, 0xac, 0x76, 0xe7, 0xb1, 0x86, 0xc3, 0xed, 0x69, 0x7a, 0x11, 0x71, 0x10, 0x26, 0xb9, 0xbc, 0x22, 0x71, 0x1e, 0x25, 0xb1, 0xc6, 0xec, 0xd4, 0xe0, 0x61, 0xa4, 0x7b, 0x26, 0x5a, 0xbf, 0x70, 0xbf, 0x62, 0x5e, 0xbf, 0x6f, 0xdc, 0x34, 0xdf, 0x89, 0x6a, 0x87, 0xf3, 0x6f, 0x37, 0x75, 0x2e, 0xa9, 0xa4, 0x67, 0xf6, 0x90, 0x97, 0xee, 0x36, 0xfb, 0x12, 0x5f, 0x23, 0xe7, 0x98, 0xa1, 0xf9, 0x4a, 0xe2, 0x48, 0xdc, 0xa1, 0x94, 0x6d, 0x81, 0x59 };
Repeat steps 2-4 above for the
secondaryContent
array. (Note in step 4 where you should changeoriginalSignature
tosecondarySignature
.)Copy the resulting arrays (
originalSignature
andsecondarySignature
) tosecure_fs.c
above the thread function._const char originalContent[] = "This is the original content!"; _const char originalSignature[] = {0x28, 0xfa, 0x72, 0xea, 0x9e, 0xfe, 0xd2, 0x8c, 0x00, 0x72, 0x04, 0xd4, 0x02, 0xfb, 0x35, 0x16, 0x31, 0xc9, 0x6d, 0xd8, 0x6d, 0xc5, 0x26, 0x8e, 0x75, 0xe4, 0x36, 0xb2, 0xf3, 0xdf, 0xac, 0x1d, 0x5a, 0x4f, 0x80, 0x46, 0xf4, 0x1e, 0x65, 0xda, 0xc0, 0x05, 0xbf, 0x29, 0xff, 0x79, 0x6d, 0x7d, 0xa4, 0x29, 0x79, 0x54, 0xd5, 0x76, 0x58, 0x57, 0x5f, 0xf4, 0xf9, 0x53, 0x7b, 0x48, 0x6b, 0x1c, 0x7c, 0xd9, 0xb1, 0x8e, 0xdf, 0x92, 0xc0, 0x4e, 0x86, 0x0e, 0x0a, 0xa9, 0x1c, 0x76, 0xf0, 0x53, 0xf1, 0xef, 0xd2, 0x4a, 0xb9, 0xc6, 0xea, 0xb1, 0xd8, 0x11, 0xdf, 0x33, 0x4d, 0x74, 0x1a, 0xbe, 0xb8, 0x68, 0xae, 0xee, 0x2d, 0x5a, 0xb3, 0xa5, 0x12, 0x56, 0x96, 0x44, 0x53, 0xea, 0x3d, 0xe8, 0x5b, 0xb8, 0x85, 0x75, 0x70, 0xb9, 0x34, 0x50, 0xb0, 0xa5, 0xaf, 0x6a, 0x8d, 0xde, 0x4e, 0xe0, 0x15, 0x37, 0x2d, 0xe0, 0x39, 0xd4, 0xfa, 0x13, 0x73, 0x3a, 0x21, 0x9d, 0x16, 0xaa, 0xf3, 0xb8, 0xf0, 0x9b, 0x76, 0xfc, 0xc3, 0x7d, 0xee, 0xd4, 0x5e, 0x3c, 0x9c, 0x0e, 0xe7, 0xde, 0x88, 0x0d, 0x2f, 0x93, 0xe4, 0xb0, 0x42, 0xe9, 0x94, 0x10, 0x69, 0x3b, 0x4b, 0xb6, 0x06, 0x0d, 0x87, 0xd1, 0x74, 0x6e, 0x6c, 0xf4, 0x85, 0x6a, 0xf5, 0xa3, 0xac, 0x76, 0xe7, 0xb1, 0x86, 0xc3, 0xed, 0x69, 0x7a, 0x11, 0x71, 0x10, 0x26, 0xb9, 0xbc, 0x22, 0x71, 0x1e, 0x25, 0xb1, 0xc6, 0xec, 0xd4, 0xe0, 0x61, 0xa4, 0x7b, 0x26, 0x5a, 0xbf, 0x70, 0xbf, 0x62, 0x5e, 0xbf, 0x6f, 0xdc, 0x34, 0xdf, 0x89, 0x6a, 0x87, 0xf3, 0x6f, 0x37, 0x75, 0x2e, 0xa9, 0xa4, 0x67, 0xf6, 0x90, 0x97, 0xee, 0x36, 0xfb, 0x12, 0x5f, 0x23, 0xe7, 0x98, 0xa1, 0xf9, 0x4a, 0xe2, 0x48, 0xdc, 0xa1, 0x94, 0x6d, 0x81, 0x59}; _const char secondaryContent[] = "Overridden by the secondary content..."; _const char secondarySignature[] = {0xb4, 0x89, 0x06, 0xbc, 0x62, 0x8a, 0x68, 0x83, 0x04, 0xd4, 0x60, 0x08, 0xd6, 0xe4, 0xe7, 0x88, 0x81, 0xb6, 0xa0, 0xdc, 0x6c, 0x27, 0xc1, 0x40, 0x4f, 0x1f, 0xfd, 0x1a, 0xf4, 0xfd, 0x53, 0xb6, 0xfc, 0xd3, 0x0e, 0x6a, 0xfc, 0xb9, 0x0b, 0xe9, 0xa6, 0x54, 0x9b, 0xa5, 0x64, 0x36, 0xd6, 0x4d, 0x10, 0x32, 0x7d, 0x2f, 0x88, 0x70, 0x20, 0x16, 0x7e, 0x1c, 0xba, 0x6f, 0x4f, 0x4d, 0x05, 0xa0, 0x1b, 0xfb, 0x15, 0x11, 0x30, 0x8c, 0x45, 0x3c, 0xab, 0x5b, 0x9e, 0x4f, 0x8d, 0x6b, 0x9a, 0x19, 0xaf, 0x5d, 0x6f, 0x71, 0x47, 0x75, 0x3c, 0x62, 0x67, 0x51, 0xe9, 0xbf, 0xc4, 0x12, 0x6a, 0xb6, 0x30, 0xf3, 0xb4, 0x6b, 0x46, 0xb6, 0xa8, 0x79, 0x6f, 0xcf, 0x3a, 0x33, 0x3d, 0x36, 0x9a, 0x98, 0x73, 0x43, 0x5f, 0x16, 0x2d, 0x89, 0xfe, 0x5b, 0xbb, 0x5f, 0x80, 0x5c, 0x06, 0x53, 0xda, 0xa5, 0x04, 0x0e, 0x89, 0xb5, 0x85, 0x46, 0xd8, 0x15, 0x97, 0xef, 0xa0, 0x31, 0x5c, 0x67, 0x85, 0x8c, 0x61, 0x45, 0xf6, 0x07, 0x11, 0x56, 0xd0, 0xfc, 0xd4, 0x10, 0x07, 0xb5, 0x28, 0xc0, 0x9d, 0x10, 0xfc, 0xff, 0xf9, 0x33, 0xed, 0xb7, 0xc4, 0xf0, 0xc1, 0xef, 0x47, 0x36, 0x49, 0xd1, 0x7d, 0xc0, 0x5f, 0x67, 0xd7, 0xf1, 0x34, 0xa3, 0x22, 0x16, 0x66, 0x88, 0x46, 0x24, 0x9d, 0x35, 0xdb, 0xb3, 0xbd, 0x38, 0xe6, 0x27, 0x04, 0xaf, 0x31, 0xda, 0x4d, 0xdf, 0x7e, 0x5e, 0xda, 0x6f, 0x55, 0x6c, 0x5e, 0xe4, 0x61, 0x23, 0x62, 0xf9, 0x54, 0x9e, 0xfd, 0x4c, 0xd0, 0x5b, 0xbc, 0x62, 0x55, 0xb1, 0x6a, 0xbc, 0x3c, 0x11, 0x53, 0xda, 0x4d, 0x7d, 0xc5, 0xad, 0xad, 0x10, 0x52, 0xf2, 0x2b, 0x4f, 0xba, 0xd2, 0x1c, 0xe1, 0xec, 0x33, 0x51, 0xdf, 0x32, 0x3a, 0x12, 0x3d, 0xbf, 0x48, 0x35, 0x84};
secure_fs.c
The signature is authenticated when a file (opened for write) is closed. Add the signature with the certificate file to
sl_FsClose()
calls:RetVal = sl_FsClose(fd, TEST_CERTIFICATE, (const unsigned char *)originalSignature, sizeof(originalSignature));
secure_fs :: secureFSExampleThread()
RetVal = sl_FsClose(fd, TEST_CERTIFICATE, (const unsigned char *)secondarySignature, strlen(secondarySignature));
secure_fs :: secureFSExampleThread()
Compile and run the example.
Further Reading
For more information and implmentation options, see the following resources:
- CC3x20, CC3x35 Network Processor Programmer's Guide: This guide contains information on how to use the SimpleLink API for writing WLAN-enabled applications. Please refer to chapter 8 (Secure File System) for further details on this lab's topic.
- UniFlash ImageCreator User's Guide: This imaging tool manually stores files on the external serial flash. Image may include the SimpleLink firmware patch file and any configuration files, security certificates, web pages, etc.
- SimpleLink Wi-Fi Certificates Handling User's Guide: This guide provides an overview on generating, installing, and using certificates and describes features specific to CC3x20 and CC3x35.
Technical support
For any questions you might have, please search on the TI SimpleLink Wi-Fi E2E Forum