diff --git a/.gitmodules b/.gitmodules index 0fd5ec3505..8669940236 100644 --- a/.gitmodules +++ b/.gitmodules @@ -33,3 +33,7 @@ path = libraries/standard/backoffAlgorithm branch = main url = https://github.com/FreeRTOS/backoffAlgorithm.git +[submodule "libraries/aws/ota-for-aws-iot-embedded-sdk"] + path = libraries/aws/ota-for-aws-iot-embedded-sdk + branch = main + url = https://github.com/aws/ota-for-aws-iot-embedded-sdk.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 169f8635ae..770fefcb7d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -101,4 +101,3 @@ if(${BUILD_TESTS}) add_subdirectory(${DIR_PATH}) endforeach() endif() - diff --git a/demos/lexicon.txt b/demos/lexicon.txt index 49a3656a9f..2466f429d0 100644 --- a/demos/lexicon.txt +++ b/demos/lexicon.txt @@ -1,7 +1,9 @@ + accel ack acks addr +addresslength aead aes aesni @@ -37,12 +39,16 @@ baltimorecybertrustroot bhargavan bignum bio +bitmasking bn +bootable boston bp +br bsd bufferlength buffersize +bytesread bytesreceived bytessent bzero @@ -92,6 +98,7 @@ connectfunction connectionsarraylength const contentrangevalstr +copybrief coremqtt corepkcs correclty @@ -142,6 +149,7 @@ dp drbg dsa dtls +dummydata dup eap ec @@ -158,18 +166,25 @@ en enc endcond endif +epalstate +esavedagentstate establishmqttsession ethernet eventcallback extendedkeyusage familiy faqs +filerc filesize +filterindex findobjects fopen fprintf +fread freertos fs +fseek +fwrite ga galois gcc @@ -248,7 +263,6 @@ kwp lastcontrolpacketsent latestversion len -lenghth leurent libc libmosquitto @@ -269,6 +283,7 @@ matchtopic mbed mbedtls mbedtlssl +mcu md mechanims mem @@ -294,6 +309,7 @@ mpi mpis mq mqtt +mqttcontext mqttkeepalivetimeout mqttprocessincomingpacket mqttsubackfailure @@ -329,10 +345,14 @@ optionalquery org os ota +otaagentstatestopped +otafile +otapalimagestatevalid outform outgoingpublishpackets outlength overriden +pacdata packetid packetidentifier packetsreceived @@ -343,9 +363,11 @@ pake param params pathlen +pathlength payloadlength pbe pbkdf +pbuf pbuffer pcdescription pcks @@ -356,6 +378,8 @@ pdeserializedinfo pdf pdigest pem +pfile +pfilepath pfilesize pfixedbuffer phost @@ -370,6 +394,7 @@ pkcs pki pkparse pkwrite +platformimagestate plaintext pleace pmessage @@ -398,6 +423,7 @@ ppacketinfo ppath ppathlen ppayload +ppkey pprocfile ppubinfo ppublishinfo @@ -415,6 +441,7 @@ prvobjectgeneration prvobjectimporting ps psessionpresent +psignature psk pslotlist pss @@ -467,7 +494,9 @@ resubscribe resubscription retrievehttpresponse retryutils +returnstatus rfc +rfcxh ripemd rng rom @@ -484,9 +513,11 @@ sendupdate serverhost setkey sha +sha256 shadowstatus shasum sig +signer signinit sizeof slotcount @@ -541,8 +572,10 @@ udbl udp udpportsarraylength uint +ulblocksize uldatalength uldigestlength +uloffset ulong ulslotcount unix @@ -560,8 +593,10 @@ usb userguide util utils +v1 ve verifyinit +vtaskdelay weierstrass wikipedia www diff --git a/demos/mqtt/mqtt_demo_subscription_manager/subscription-manager/CMakeLists.txt b/demos/mqtt/mqtt_demo_subscription_manager/subscription-manager/CMakeLists.txt index 7d0f3b1796..336fcc3a51 100644 --- a/demos/mqtt/mqtt_demo_subscription_manager/subscription-manager/CMakeLists.txt +++ b/demos/mqtt/mqtt_demo_subscription_manager/subscription-manager/CMakeLists.txt @@ -12,7 +12,7 @@ add_library( ${LIBRARY_NAME} target_include_directories( ${LIBRARY_NAME} PUBLIC + ${CMAKE_CURRENT_LIST_DIR} ${LOGGING_INCLUDE_DIRS} ${MQTT_INCLUDE_PUBLIC_DIRS} ) - diff --git a/demos/pkcs11/common/include/demo_helpers.h b/demos/pkcs11/common/include/demo_helpers.h index 0794fd07ff..8ae45aeef4 100644 --- a/demos/pkcs11/common/include/demo_helpers.h +++ b/demos/pkcs11/common/include/demo_helpers.h @@ -85,7 +85,7 @@ void end( CK_SESSION_HANDLE session, * * @param[in] pcDescription Description message * @param[in] pucData Hex contents to print (Public key) - * @param[in] ulDataLength Lenghth of pucData + * @param[in] ulDataLength Length of pucData * */ void writeHexBytesToConsole( char * description, diff --git a/libraries/aws/ota-for-aws-iot-embedded-sdk b/libraries/aws/ota-for-aws-iot-embedded-sdk new file mode 160000 index 0000000000..ab61d09498 --- /dev/null +++ b/libraries/aws/ota-for-aws-iot-embedded-sdk @@ -0,0 +1 @@ +Subproject commit ab61d094987ae749ba8b09fa6d63a56878930288 diff --git a/platform/CMakeLists.txt b/platform/CMakeLists.txt index 3fdffa59a9..d10c35aea2 100644 --- a/platform/CMakeLists.txt +++ b/platform/CMakeLists.txt @@ -1,2 +1,13 @@ +set(OPENSSL_USE_STATIC_LIBS TRUE) +find_package(OpenSSL REQUIRED) + +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) + +find_library(LIB_RT rt REQUIRED) + # Add the posix targets add_subdirectory( ${PLATFORM_DIR}/posix ) + +# Add ota porting targets +# add_subdirectory( ${PLATFORM_DIR}/posix/ota_pal ) diff --git a/platform/lexicon.txt b/platform/lexicon.txt index 7d2ad211f7..0ef0d142df 100644 --- a/platform/lexicon.txt +++ b/platform/lexicon.txt @@ -9,44 +9,62 @@ aws backoff backoffdelay basedefs +bitmasking bool +bootable +bootloader br buf +bytesread bytesreceived bytessent bytestorecv bytestosend ca +cert cmock com connectsuccessindex const couldn coverity +crt +csdk cwd didn dns +dummydata eagain +ecdsa endcode endif enum enums +epalstate errno errornumber +esavedagentstate ewouldblock +exe expectedstatus fclose fd +filehandle filelabel filepath filepaths +filerc filetype -fixme +feof fopen +fread +freertos +fseek functionname functionpage functionspage functiontofail +fwrite getaddrinfo getcwd hostnamelength @@ -54,6 +72,7 @@ html http https ifndef +imagestatefile implemenation inc int @@ -66,6 +85,7 @@ longjmp malloc maxattempts maxfragmentlength +mcu messagelevel mfln min @@ -74,53 +94,83 @@ mqtt mynetworkrecvimplementation mynetworksendimplementation mytcpsocketcontext +mythreadsleepfunction mytime mytimefunction -mythreadsleepfunction mytlscontext nanosleep networkcontext nextjittermax noninfringement +ok onlinepubs opengroup openssl openssl_invalid_parameter org +ota +otafile +otaimagestateaborted +otaimagestateaccepted +otaimagestaterejected +otaimagestateinvalid +otaimagestatependingcommit +otaimagestateunknown +otapalimagestateinvalid +otapalimagestatependingcommit +otapalimagestateunknown +otapalimagestatevalid paddrinfo palpnprotos param +pbuf pbuffer +pcdata +pcertfilepath pclientcertpath pem +pfile +pfilepath pformat phostname +platformimagestate plaintext platfrom +platformimagestate plisthead pnetworkcontext png popensslcredentials posix +ppkey +pplatformimagestate pprivatekeypath +pre pretryparams prootcapath pserverinfo +psignature pssl psslcontext ptcpsocket ramdom rand +realfilepath reconnectparam recv recvtimeout recvtimeoutms retvalue +rfcxh +rsa sdk sendtimeout sendtimeoutms serverinfo +sha256 sigalrm +signer +sizeof sleeptimems sni snihostname @@ -138,6 +188,7 @@ sys tcp tcpsocket tcpsocketcontext +thingname timeinseconds timespec tls @@ -150,6 +201,34 @@ transportpage transportsectionimplementation transportsectionoverview transportstruct +txt +ulblockindex +ulblocksize +uloffset unistd +utest utils +v1 +vtaskdelay +writesize www +blocksize +config +otapalsuccess +otapaluninitialized +otapaloutofmemory +otapalnullfilecontext +otapalsignaturecheckfailed +otapalrxfilecreatefailed +otapalrxfiletoolarge +otapalbootinfocreatefailed +otapalbadsignercert +otapalbadimagestate +otapalabortfailed +otapalrejectfailed +otapalcommitfailed +otapalactivatefailed +otapalfileabort +otapalfileclose +pdata +pfilecontext \ No newline at end of file diff --git a/platform/posix/ota_pal/CMakeLists.txt b/platform/posix/ota_pal/CMakeLists.txt new file mode 100644 index 0000000000..3a6d4cf107 --- /dev/null +++ b/platform/posix/ota_pal/CMakeLists.txt @@ -0,0 +1,19 @@ +add_library(ota_pal INTERFACE) +target_sources(ota_pal + INTERFACE + "${CMAKE_CURRENT_SOURCE_DIR}/source/ota_pal_posix.c" +) + +target_include_directories( ota_pal + INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/source/include + ${LOGGING_INCLUDE_DIRS} +) + +target_link_libraries( ota_pal + INTERFACE ${OPENSSL_CRYPTO_LIBRARY} +) + +if(BUILD_TESTS) + add_subdirectory(utest) +endif() diff --git a/platform/posix/ota_pal/source/include/ota_pal_posix.h b/platform/posix/ota_pal/source/include/ota_pal_posix.h new file mode 100644 index 0000000000..2b367e7e66 --- /dev/null +++ b/platform/posix/ota_pal/source/include/ota_pal_posix.h @@ -0,0 +1,224 @@ +/* + * FreeRTOS OTA V2.0.0 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +#ifndef _OTA_PAL_H_ +#define _OTA_PAL_H_ + +#include "ota.h" + +/** + * @brief Code signing certificate + * + * The certificate is used for OTA image signing. If a platform does not support a file + * system the signing certificate can be pasted here for testing purpose. + * + * PEM-encoded code signer certificate + * + * Must include the PEM header and footer: + * "-----BEGIN CERTIFICATE-----\n" + * "...base64 data...\n" + * "-----END CERTIFICATE-----\n"; + */ +static const char signingcredentialSIGNING_CERTIFICATE_PEM[] = "Paste code signing certificate here."; + +/** + * @brief Maximum file path length on Linux + */ +#define OTA_FILE_PATH_LENGTH_MAX 512 + +/** + * @brief Abort an OTA transfer. + * + * Aborts access to an existing open file represented by the OTA file context pFileContext. This is + * only valid for jobs that started successfully. + * + * @note The input OtaFileContext_t pFileContext is checked for NULL by the OTA agent before this + * function is called. + * This function may be called before the file is opened, so the file pointer pFileContext->fileHandle + * may be NULL when this function is called. + * + * @param[in] pFileContext OTA file context information. + * + * @return The OTA PAL layer error code combined with the MCU specific error code. See OTA Agent + * error codes information in ota.h. + * + * The file pointer will be set to NULL after this function returns. + * OtaPalSuccess is returned when aborting access to the open file was successful. + * OtaPalFileAbort is returned when aborting access to the open file context was unsuccessful. + */ +OtaPalStatus_t otaPal_Abort( OtaFileContext_t * const pFileContext ); + +/** + * @brief Create a new receive file for the data chunks as they come in. + * + * @note Opens the file indicated in the OTA file context in the MCU file system. + * + * @note The previous image may be present in the designated image download partition or file, so the + * partition or file must be completely erased or overwritten in this routine. + * + * @note The input OtaFileContext_t pFileContext is checked for NULL by the OTA agent before this + * function is called. + * The device file path is a required field in the OTA job document, so pFileContext->pFilePath is + * checked for NULL by the OTA agent before this function is called. + * + * @param[in] pFileContext OTA file context information. + * + * @return The OTA PAL layer error code combined with the MCU specific error code. See OTA Agent + * error codes information in ota.h. + * + * OtaPalSuccess is returned when file creation is successful. + * OtaPalRxFileTooLarge is returned if the file to be created exceeds the device's non-volatile memory size constraints. + * OtaPalBootInfoCreateFailed is returned if the bootloader information file creation fails. + * OtaPalRxFileCreateFailed is returned for other errors creating the file in the device's non-volatile memory. + */ +OtaPalStatus_t otaPal_CreateFileForRx( OtaFileContext_t * const pFileContext ); + +/* @brief Authenticate and close the underlying receive file in the specified OTA context. + * + * @note The input OtaFileContext_t pFileContext is checked for NULL by the OTA agent before this + * function is called. This function is called only at the end of block ingestion. + * otaPal_CreateFileForRx() must succeed before this function is reached, so + * pFileContext->fileHandle(or pFileContext->pFile) is never NULL. + * The certificate path on the device is a required job document field in the OTA Agent, + * so pFileContext->pCertFilepath is never NULL. + * The file signature key is required job document field in the OTA Agent, so pFileContext->pSignature will + * never be NULL. + * + * If the signature verification fails, file close should still be attempted. + * + * @param[in] pFileContext OTA file context information. + * + * @return The OTA PAL layer error code combined with the MCU specific error code. See OTA Agent + * error codes information in ota.h. + * + * OtaPalSuccess is returned on success. + * OtaPalSignatureCheckFailed is returned when cryptographic signature verification fails. + * OtaPalBadSignerCert is returned for errors in the certificate itself. + * OtaPalFileClose is returned when closing the file fails. + */ +OtaPalStatus_t otaPal_CloseFile( OtaFileContext_t * const pFileContext ); + +/** + * @brief Write a block of data to the specified file at the given offset. + * + * @note The input OtaFileContext_t pFileContext is checked for NULL by the OTA agent before this + * function is called. + * The file pointer/handle pFileContext->pFile, is checked for NULL by the OTA agent before this + * function is called. + * pData is checked for NULL by the OTA agent before this function is called. + * blockSize is validated for range by the OTA agent before this function is called. + * offset is validated by the OTA agent before this function is called. + * + * @param[in] pFileContext OTA file context information. + * @param[in] offset Byte offset to write to from the beginning of the file. + * @param[in] pData Pointer to the byte array of data to write. + * @param[in] blockSize The number of bytes to write. + * + * @return The number of bytes written on a success, or a negative error code from the platform + * abstraction layer. + */ +int16_t otaPal_WriteBlock( OtaFileContext_t * const pFileContext, + uint32_t ulOffset, + uint8_t * const pcData, + uint32_t ulBlockSize ); + +/** + * @brief Activate the newest MCU image received via OTA. + * + * This function activates the newest firmware received via OTA. It is typically just a reset of + * the device. + * + * @note This function SHOULD not return. If it does, the platform does not support + * an automatic reset or an error occurred. + * + * @return The OTA PAL layer error code combined with the MCU specific error code. See OTA Agent + * error codes information in ota.h. + */ +OtaPalStatus_t otaPal_ActivateNewImage( OtaFileContext_t * const pFileContext ); + +/** + * @brief Reset the device. + * + * This function shall reset the MCU and cause a reboot of the system. + * + * @note This function SHOULD not return. If it does, the platform does not support + * an automatic reset or an error occurred. + * + * @return The OTA PAL layer error code combined with the MCU specific error code. See OTA Agent + * error codes information in ota.h. + */ + +OtaPalStatus_t otaPal_ResetDevice( OtaFileContext_t * const pFileContext ); + +/** + * @brief Attempt to set the state of the OTA update image. + * + * Do whatever is required by the platform to Accept/Reject the OTA update image (or bundle). + * Refer to the PAL implementation to determine what happens on your platform. + * + * @param[in] pFileContext File context of type OtaFileContext_t. + * + * @param[in] eState The desired state of the OTA update image. + * + * @return The OtaPalStatus_t error code combined with the MCU specific error code. See ota.h for + * OTA major error codes and your specific PAL implementation for the sub error code. + * + * Major error codes returned are: + * + * OtaPalSuccess on success. + * OtaPalBadImageState: if you specify an invalid OtaImageState_t. No sub error code. + * OtaPalAbortFailed: failed to roll back the update image as requested by OtaImageStateAborted. + * OtaPalRejectFailed: failed to roll back the update image as requested by OtaImageStateRejected. + * OtaPalCommitFailed: failed to make the update image permanent as requested by OtaImageStateAccepted. + */ +OtaPalStatus_t otaPal_SetPlatformImageState( OtaFileContext_t * const pFileContext, + OtaImageState_t eState ); + +/** + * @brief Get the state of the OTA update image. + * + * We read this at OTA_Init time and when the latest OTA job reports itself in self + * test. If the update image is in the "pending commit" state, we start a self test + * timer to assure that we can successfully connect to the OTA services and accept + * the OTA update image within a reasonable amount of time (user configurable). If + * we don't satisfy that requirement, we assume there is something wrong with the + * firmware and automatically reset the device, causing it to roll back to the + * previously known working code. + * + * If the update image state is not in "pending commit," the self test timer is + * not started. + * + * @param[in] pFileContext File context of type OtaFileContext_t. + * + * @return An OtaPalImageState_t. One of the following: + * OtaPalImageStatePendingCommit (the new firmware image is in the self test phase) + * OtaPalImageStateValid (the new firmware image is already committed) + * OtaPalImageStateInvalid (the new firmware image is invalid or non-existent) + * + * NOTE: OtaPalImageStateUnknown should NEVER be returned and indicates an implementation error. + */ +OtaPalImageState_t otaPal_GetPlatformImageState( OtaFileContext_t * const pFileContext ); + +#endif /* ifndef _OTA_PAL_H_ */ diff --git a/platform/posix/ota_pal/source/ota_pal_posix.c b/platform/posix/ota_pal/source/ota_pal_posix.c new file mode 100644 index 0000000000..620ff09a51 --- /dev/null +++ b/platform/posix/ota_pal/source/ota_pal_posix.c @@ -0,0 +1,666 @@ +/* + * OTA PAL for POSIX V2.0.0 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + + +/* OTA PAL implementation for POSIX platform. */ + +#include +#include +#include +#include +#include +#include + +#include "ota.h" +#include "ota_pal_posix.h" + +#include +#include +#include +#include + +/** + * @brief Size of buffer used in file operations on this platform (POSIX). + */ +#define OTA_PAL_POSIX_BUF_SIZE ( ( size_t ) 4096U ) + +/** + * @brief Specify the OTA signature algorithm we support on this platform. + */ +const char OTA_JsonFileSignatureKey[ OTA_FILE_SIG_KEY_STR_MAX_LENGTH ] = "sig-sha256-ecdsa"; + +/** + * @brief Read the specified signer certificate from the filesystem into a local buffer. The allocated + * memory becomes the property of the caller who is responsible for freeing it. + */ +static EVP_PKEY * Openssl_GetPkeyFromCertificate( uint8_t * pCertFilePath ); + +/** + * @brief Verify the signature of the input content with OpenSSL. + */ +static OtaPalMainStatus_t Openssl_DigestVerify( EVP_MD_CTX * pSigContext, + EVP_PKEY * pPkey, + FILE * pFile, + Sig256_t * pSignature ); + +/** + * @brief Verify the signature of the specified file using OpenSSL. + */ +static OtaPalStatus_t otaPal_CheckFileSignature( OtaFileContext_t * const C ); + +/*-----------------------------------------------------------*/ + +static EVP_PKEY * Openssl_GetPkeyFromCertificate( uint8_t * pCertFilePath ) +{ + BIO * pBio = NULL; + X509 * pCert = NULL; + EVP_PKEY * pPkey = NULL; + int32_t rc = 0; + + /* Read the cert file */ + pBio = BIO_new( BIO_s_file() ); + + if( pBio != NULL ) + { + /* coverity[misra_c_2012_rule_10_1_violation] */ + rc = BIO_read_filename( pBio, pCertFilePath ); + + if( rc != 1 ) + { + LogDebug( ( " No cert file, reading signer cert from header file\n" ) ); + + /* Get the signer cert from a predefined PEM string */ + BIO_free_all( pBio ); + pBio = BIO_new( BIO_s_mem() ); + + if( pBio != NULL ) + { + rc = BIO_puts( pBio, signingcredentialSIGNING_CERTIFICATE_PEM ); + + if( rc <= 0 ) + { + LogError( ( "Failed to write a PEM string to BIO stream" ) ); + } + } + else + { + LogError( ( "Failed to read certificate from a PEM string." ) ); + } + } + else + { + LogError( ( "Failed to read certificate from a file." ) ); + } + } + + if( ( pBio != NULL ) && ( rc > 0 ) ) + { + pCert = PEM_read_bio_X509( pBio, NULL, NULL, NULL ); + + if( pCert != NULL ) + { + LogDebug( ( "Getting the pkey from the X509 cert." ) ); + + /* Extract the public key */ + pPkey = X509_get_pubkey( pCert ); + + if( pPkey == NULL ) + { + LogError( ( "Failed to get pkey from the signer cert." ) ); + } + } + else + { + LogError( ( "Failed to load cert from either file or predefined string." ) ); + } + } + else + { + LogError( ( "Failed to read signer cert." ) ); + } + + BIO_free_all( pBio ); + X509_free( pCert ); + + /* pPkey should be freed by the caller */ + return pPkey; +} + + +static OtaPalMainStatus_t Openssl_DigestVerifyStart( EVP_MD_CTX * pSigContext, + EVP_PKEY * pPkey, + FILE * pFile, + uint8_t ** pBuf ) +{ + OtaPalMainStatus_t mainErr = OtaPalSignatureCheckFailed; + + assert( pBuf != NULL ); + + /* Verify an ECDSA-SHA256 signature. */ + if( ( pSigContext != NULL ) && + ( pPkey != NULL ) && + ( pFile != NULL ) && + ( 1 == EVP_DigestVerifyInit( pSigContext, NULL, EVP_sha256(), NULL, pPkey ) ) ) + { + LogDebug( ( "Started signature verification." ) ); + + *pBuf = OPENSSL_malloc( OTA_PAL_POSIX_BUF_SIZE ); + + if( *pBuf == NULL ) + { + LogError( ( "Failed to allocate buffer memory." ) ); + mainErr = OtaPalOutOfMemory; + } + else + { + mainErr = OtaPalSuccess; + } + } + else + { + LogError( ( "File signature check failed at INIT." ) ); + } + + return mainErr; +} + +static bool Openssl_DigestVerifyUpdate( EVP_MD_CTX * pSigContext, + FILE * pFile, + uint8_t * pBuf ) +{ + size_t bytesRead; + + do + { + /* POSIX port using standard library */ + /* coverity[misra_c_2012_rule_21_6_violation] */ + bytesRead = fread( pBuf, 1U, OTA_PAL_POSIX_BUF_SIZE, pFile ); + + /* feof returns non-zero if end of file is reached, otherwise it returns 0. When + * bytesRead is not equal to OTA_PAL_POSIX_BUF_SIZE, we should be reading last + * chunk and reach to end of file. */ + if( ( bytesRead < OTA_PAL_POSIX_BUF_SIZE ) && ( 0 == feof( pFile ) ) ) + { + break; + } + + /* Include the file chunk in the signature validation. Zero size is OK. */ + if( 1 != EVP_DigestVerifyUpdate( pSigContext, pBuf, bytesRead ) ) + { + break; + } + } while( bytesRead > 0UL ); + + return feof( pFile ) != 0; +} + +static OtaPalMainStatus_t Openssl_DigestVerify( EVP_MD_CTX * pSigContext, + EVP_PKEY * pPkey, + FILE * pFile, + Sig256_t * pSignature ) +{ + OtaPalMainStatus_t mainErr = OtaPalSignatureCheckFailed; + OtaPalMainStatus_t startErr; + uint8_t * pBuf; + + startErr = Openssl_DigestVerifyStart( pSigContext, pPkey, pFile, &pBuf ); + + if( OtaPalSuccess == startErr ) + { + /* Rewind the received file to the beginning. */ + /* POSIX port using standard library */ + /* coverity[misra_c_2012_rule_21_6_violation] */ + if( fseek( pFile, 0L, SEEK_SET ) == 0 ) + { + bool eof = Openssl_DigestVerifyUpdate( pSigContext, pFile, pBuf ); + + if( ( eof == true ) && ( 1 == EVP_DigestVerifyFinal( pSigContext, + pSignature->data, + pSignature->size ) ) ) + { + mainErr = OtaPalSuccess; + } + else + { + LogError( ( "File signature check failed at FINAL" ) ); + } + } + + /* Free the temporary file page buffer. */ + OPENSSL_free( pBuf ); + } + else + { + mainErr = startErr; + } + + return mainErr; +} + +static OtaPalStatus_t otaPal_CheckFileSignature( OtaFileContext_t * const C ) +{ + OtaPalMainStatus_t mainErr = OtaPalSignatureCheckFailed; + EVP_PKEY * pPkey = NULL; + EVP_MD_CTX * pSigContext = NULL; + + if( C != NULL ) + { + /* Extract the signer cert from the file */ + pPkey = Openssl_GetPkeyFromCertificate( C->pCertFilepath ); + + /* Create a new signature context for verification purpose */ + pSigContext = EVP_MD_CTX_new(); + + if( ( pPkey != NULL ) && ( pSigContext != NULL ) ) + { + /* Verify an ECDSA-SHA256 signature. */ + mainErr = Openssl_DigestVerify( pSigContext, pPkey, C->pFile, C->pSignature ); + } + else + { + if( pSigContext == NULL ) + { + LogError( ( "File signature check failed at NEW sig context." ) ); + } + else + { + LogError( ( "File signature check failed at EXTRACT pkey from signer certificate." ) ); + mainErr = OtaPalBadSignerCert; + } + } + + /* Free up objects */ + EVP_MD_CTX_free( pSigContext ); + EVP_PKEY_free( pPkey ); + } + else + { + LogError( ( "Failed to check file signature: Paramater check failed: " + " Invalid OTA file context." ) ); + /* Invalid OTA context or file pointer. */ + mainErr = OtaPalNullFileContext; + } + + return OTA_PAL_COMBINE_ERR( mainErr, 0 ); +} + +/*-----------------------------------------------------------*/ + +OtaPalStatus_t otaPal_Abort( OtaFileContext_t * const C ) +{ + /* Set default return status to uninitialized. */ + OtaPalMainStatus_t mainErr = OtaPalUninitialized; + OtaPalSubStatus_t subErr = 0; + int32_t lFileCloseResult; + + if( NULL != C ) + { + /* Close the OTA update file if it's open. */ + if( NULL != C->pFile ) + { + /* POSIX port using standard library */ + /* coverity[misra_c_2012_rule_21_6_violation] */ + lFileCloseResult = fclose( C->pFile ); + C->pFile = NULL; + + if( 0 == lFileCloseResult ) + { + LogInfo( ( "Closed file." ) ); + mainErr = OtaPalSuccess; + } + else /* Failed to close file. */ + { + LogError( ( "Failed to close file." ) ); + mainErr = OtaPalFileAbort; + subErr = errno; + } + } + else + { + /* Nothing to do. No open file associated with this context. */ + mainErr = OtaPalSuccess; + } + } + else /* Context was not valid. */ + { + LogError( ( "Parameter check failed: Input is NULL." ) ); + mainErr = OtaPalFileAbort; + } + + return OTA_PAL_COMBINE_ERR( mainErr, subErr ); +} + +OtaPalStatus_t otaPal_CreateFileForRx( OtaFileContext_t * const C ) +{ + OtaPalStatus_t result = OTA_PAL_COMBINE_ERR( OtaPalUninitialized, 0 ); + char realFilePath[ OTA_FILE_PATH_LENGTH_MAX ]; + + if( C != NULL ) + { + if( C->pFilePath != NULL ) + { + if( C->pFilePath[ 0 ] != '/' ) + { + int res = snprintf( realFilePath, OTA_FILE_PATH_LENGTH_MAX, "%s/%s", getenv( "PWD" ), C->pFilePath ); + assert( res >= 0 ); + ( void ) res; /* Suppress the unused variable warning when assert is off. */ + } + else + { + strncpy( realFilePath, ( const char * ) C->pFilePath, strlen( ( const char * ) C->pFilePath ) + 1 ); + } + + /* POSIX port using standard library */ + /* coverity[misra_c_2012_rule_21_6_violation] */ + C->pFile = fopen( ( const char * ) realFilePath, "w+b" ); + + if( C->pFile != NULL ) + { + result = OTA_PAL_COMBINE_ERR( OtaPalSuccess, 0 ); + LogInfo( ( "Receive file created." ) ); + } + else + { + result = OTA_PAL_COMBINE_ERR( OtaPalRxFileCreateFailed, errno ); + LogError( ( "Failed to start operation: Operation already started. failed to open -- %s Path ", C->pFilePath ) ); + } + } + else + { + result = OTA_PAL_COMBINE_ERR( OtaPalRxFileCreateFailed, 0 ); + LogError( ( "Invalid file path provided." ) ); + } + } + else + { + result = OTA_PAL_COMBINE_ERR( OtaPalRxFileCreateFailed, 0 ); + LogError( ( "Invalid context provided." ) ); + } + + /* Exiting function without calling fclose. Context file handle state is managed by this API. */ + return result; +} + +OtaPalStatus_t otaPal_CloseFile( OtaFileContext_t * const C ) +{ + int32_t filerc = 0; + OtaPalMainStatus_t mainErr = OtaPalSuccess; + OtaPalSubStatus_t subErr = 0; + OtaPalStatus_t result; + + if( C != NULL ) + { + if( C->pSignature != NULL ) + { + /* Verify the file signature, close the file and return the signature verification result. */ + result = otaPal_CheckFileSignature( C ); + mainErr = OTA_PAL_MAIN_ERR( result ); + subErr = OTA_PAL_SUB_ERR( result ); + } + else + { + LogError( ( "Parameter check failed: OTA signature structure is NULL." ) ); + mainErr = OtaPalSignatureCheckFailed; + } + + /* Close the file. */ + /* POSIX port using standard library */ + /* coverity[misra_c_2012_rule_21_6_violation] */ + filerc = fclose( C->pFile ); + C->pFile = NULL; + + if( filerc != 0 ) + { + LogError( ( "Failed to close OTA update file." ) ); + mainErr = OtaPalFileClose; + subErr = errno; + } + + if( mainErr == OtaPalSuccess ) + { + LogInfo( ( "%s signature verification passed.", OTA_JsonFileSignatureKey ) ); + + ( void ) otaPal_SetPlatformImageState( C, OtaImageStateTesting ); + } + else + { + LogError( ( "Failed to pass %s signature verification: %d.", + OTA_JsonFileSignatureKey, OTA_PAL_COMBINE_ERR( mainErr, subErr ) ) ); + + /* If we fail to verify the file signature that means the image is not valid. We need to set the image state to aborted. */ + ( void ) otaPal_SetPlatformImageState( C, OtaImageStateAborted ); + } + } + else /* Invalid OTA Context. */ + { + LogError( ( "Failed to close file: " + "Parameter check failed: " + "Invalid context." ) ); + mainErr = OtaPalFileClose; + } + + return OTA_PAL_COMBINE_ERR( mainErr, subErr ); +} + +int16_t otaPal_WriteBlock( OtaFileContext_t * const C, + uint32_t ulOffset, + uint8_t * const pcData, + uint32_t ulBlockSize ) +{ + int32_t filerc = 0; + size_t writeSize = 0; + + if( C != NULL ) + { + /* POSIX port using standard library */ + /* coverity[misra_c_2012_rule_21_6_violation] */ + filerc = fseek( C->pFile, ( int64_t ) ulOffset, SEEK_SET ); + + if( 0 == filerc ) + { + /* POSIX port using standard library */ + /* coverity[misra_c_2012_rule_21_6_violation] */ + writeSize = fwrite( pcData, 1, ulBlockSize, C->pFile ); + + if( writeSize != ulBlockSize ) + { + LogError( ( "Failed to write block to file: " + "fwrite returned error: " + "errno=%d", errno ) ); + + filerc = -1; + } + else + { + filerc = ( int32_t ) writeSize; + } + } + else + { + LogError( ( "fseek failed. fseek returned errno = %d", errno ) ); + filerc = -1; + } + } + else /* Invalid context or file pointer provided. */ + { + LogError( ( "Invalid context." ) ); + filerc = -1; + } + + return ( int16_t ) filerc; +} + +/* Return no error. POSIX implementation simply does nothing on activate. */ +OtaPalStatus_t otaPal_ActivateNewImage( OtaFileContext_t * const C ) +{ + ( void ) C; + + return OTA_PAL_COMBINE_ERR( OtaPalSuccess, 0 ); +} + +/* Set the final state of the last transferred (final) OTA file (or bundle). + * On POSIX, the state of the OTA image is stored in PlatformImageState.txt. */ +OtaPalStatus_t otaPal_SetPlatformImageState( OtaFileContext_t * const C, + OtaImageState_t eState ) +{ + OtaPalMainStatus_t mainErr = OtaPalBadImageState; + OtaPalSubStatus_t subErr = 0; + FILE * pPlatformImageState = NULL; + char imageStateFile[ OTA_FILE_PATH_LENGTH_MAX ]; + + + ( void ) C; + + if( ( eState != OtaImageStateUnknown ) && ( eState <= OtaLastImageState ) ) + { + int res = snprintf( imageStateFile, OTA_FILE_PATH_LENGTH_MAX, "%s/%s", getenv( "PWD" ), "PlatformImageState.txt" ); + assert( res >= 0 ); + ( void ) res; /* Suppress the unused variable warning when assert is off. */ + + /* POSIX port using standard library */ + /* coverity[misra_c_2012_rule_21_6_violation] */ + pPlatformImageState = fopen( imageStateFile, "w+b" ); + + if( pPlatformImageState != NULL ) + { + /* Write the image state to PlatformImageState.txt. */ + /* POSIX port using standard library */ + /* coverity[misra_c_2012_rule_21_6_violation] */ + if( 1UL == fwrite( &eState, sizeof( OtaImageState_t ), 1, pPlatformImageState ) ) + { + /* Close PlatformImageState.txt. */ + /* POSIX port using standard library */ + /* coverity[misra_c_2012_rule_21_6_violation] */ + if( 0 == fclose( pPlatformImageState ) ) + { + mainErr = OtaPalSuccess; + } + else + { + LogError( ( "Unable to close image state file." ) ); + subErr = errno; + } + } + else + { + LogError( ( "Unable to write to image state file. error-- %d", errno ) ); + subErr = errno; + } + } + else + { + LogError( ( "Unable to open image state file. error -- %d", errno ) ); + subErr = errno; + } + } + else /* Image state invalid. */ + { + LogError( ( "Invalid image state provided." ) ); + } + + /* Allow calls to fopen and fclose in this context. */ + return OTA_PAL_COMBINE_ERR( mainErr, subErr ); +} + +OtaPalStatus_t otaPal_ResetDevice( OtaFileContext_t * const C ) +{ + ( void ) C; + + /* Return no error. POSIX implementation does not reset device. */ + return OTA_PAL_COMBINE_ERR( OtaPalSuccess, 0 ); +} + +/* Get the state of the currently running image. + * + * On POSIX, this is simulated by looking for and reading the state from + * the PlatformImageState.txt file in the current working directory. + * + * We read this at OTA_Init time so we can tell if the MCU image is in self + * test mode. If it is, we expect a successful connection to the OTA services + * within a reasonable amount of time. If we don't satisfy that requirement, + * we assume there is something wrong with the firmware and reset the device, + * causing it to rollback to the previous code. On POSIX, this is not + * fully simulated as there is no easy way to reset the simulated device. + */ +OtaPalImageState_t otaPal_GetPlatformImageState( OtaFileContext_t * const C ) +{ + FILE * pPlatformImageState; + OtaImageState_t eSavedAgentState = OtaImageStateUnknown; + OtaPalImageState_t ePalState = OtaPalImageStateUnknown; + char imageStateFile[ OTA_FILE_PATH_LENGTH_MAX ]; + + int res = snprintf( imageStateFile, OTA_FILE_PATH_LENGTH_MAX, "%s/%s", getenv( "PWD" ), "PlatformImageState.txt" ); + + assert( res >= 0 ); + ( void ) res; /* Suppress the unused variable warning when assert is off. */ + + ( void ) C; + + /* POSIX port using standard library */ + /* coverity[misra_c_2012_rule_21_6_violation] */ + pPlatformImageState = fopen( imageStateFile, "r+b" ); + + if( pPlatformImageState != NULL ) + { + /* POSIX port using standard library */ + /* coverity[misra_c_2012_rule_21_6_violation] */ + if( 1U != fread( &eSavedAgentState, sizeof( OtaImageState_t ), 1, pPlatformImageState ) ) + { + /* If an error occurred reading the file, mark the state as aborted. */ + LogError( ( "Failed to read image state file." ) ); + ePalState = OtaPalImageStateInvalid; + } + else + { + if( eSavedAgentState == OtaImageStateTesting ) + { + ePalState = OtaPalImageStatePendingCommit; + } + else if( eSavedAgentState == OtaImageStateAccepted ) + { + ePalState = OtaPalImageStateValid; + } + else + { + ePalState = OtaPalImageStateInvalid; + } + } + + /* POSIX port using standard library */ + /* coverity[misra_c_2012_rule_21_6_violation] */ + if( 0 != fclose( pPlatformImageState ) ) + { + LogError( ( "Failed to close image state file." ) ); + ePalState = OtaPalImageStateInvalid; + } + } + else + { + /* If no image state file exists, assume a factory image. */ + ePalState = OtaPalImageStateValid; /*lint !e64 Allow assignment. */ + } + + return ePalState; +} + +/*-----------------------------------------------------------*/ diff --git a/platform/posix/ota_pal/utest/CMakeLists.txt b/platform/posix/ota_pal/utest/CMakeLists.txt new file mode 100644 index 0000000000..7e25bdb7fd --- /dev/null +++ b/platform/posix/ota_pal/utest/CMakeLists.txt @@ -0,0 +1,78 @@ +project( "ota_pal unit test" ) +cmake_minimum_required( VERSION 3.2.0 ) + +#== ================== Define your project name (edit) ======================== +set( project_name "ota_pal" ) + +#== =================== Create your mock here (edit) ======================== + +#list the files to mock here +list( APPEND mock_list + "" + ) +#list the directories your mocks need +list( APPEND mock_include_list + "" + ) +#list the definitions of your mocks to control what to be included +list( APPEND mock_define_list + "" + ) + +#== =============== Create the library under test here (edit) ================== + +#list the files you would like to test here +list( APPEND real_source_files + "${PLATFORM_DIR}/posix/ota_pal/source/ota_pal_posix.c" + ) + +#list the directories the module under test includes +list( APPEND real_include_directories + "${MODULES_DIR}/aws/ota-for-aws-iot-embedded-sdk/source/include" + "${PLATFORM_DIR}/posix/ota_pal/source/include" + ${OPENSSL_INCLUDE_DIR} + ${CMAKE_CURRENT_LIST_DIR} + ) + +#== =================== Create UnitTest Code here (edit) ===================== + +#list the directories your test needs to include +list ( APPEND test_include_directories + ${CMAKE_CURRENT_BINARY_DIR}/include + ${CMAKE_CURRENT_LIST_DIR} + ${MODULES_DIR}/aws/ota-for-aws-iot-embedded-sdk/source/include + ) + +#== =========================== (end edit) =================================== +set ( mock_name "" ) +set ( real_name "ota_pal_real" ) + + +create_real_library ( ${real_name} + "${real_source_files}" + "${real_include_directories}" + "${mock_name}" + ) + +list(APPEND utest_link_list + lib${real_name}.a + ${OPENSSL_LIBRARIES} + ) + +list ( APPEND utest_dep_list + ${real_name} + ) + +set ( utest_name "ota_pal_posix_utest" ) +set ( utest_source "ota_pal_posix_utest.c" ) +create_test ( ${utest_name} + ${utest_source} + "${utest_link_list}" + "${utest_dep_list}" + "${test_include_directories}" + ) + +# Unit tests configuration +set(OTA_PAL_UTEST_CERT_FILE "${CMAKE_CURRENT_LIST_DIR}/test_files/ecdsa-sha256-signer.crt.pem") +set(OTA_PAL_UTEST_FIRMWARE_FILE "${CMAKE_BINARY_DIR}/bin/tests/dummy.bin") +configure_file(ota_utest_config.h.in ${CMAKE_CURRENT_BINARY_DIR}/include/ota_utest_config.h) diff --git a/platform/posix/ota_pal/utest/ota_config.h b/platform/posix/ota_pal/utest/ota_config.h new file mode 100644 index 0000000000..826cb9e397 --- /dev/null +++ b/platform/posix/ota_pal/utest/ota_config.h @@ -0,0 +1,151 @@ +/* + * AWS IoT Device SDK for Embedded C V202011.00 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file ota_config.h + * @brief OTA user configurable settings. + */ + +#ifndef _OTA_CONFIG_H_ +#define _OTA_CONFIG_H_ + +/** + * @brief The number of words allocated to the stack for the OTA agent. + */ +#define otaconfigSTACK_SIZE 10000U + +/** + * @brief Log base 2 of the size of the file data block message (excluding the header). + * + * 10 bits yields a data block size of 1KB. + */ +#define otaconfigLOG2_FILE_BLOCK_SIZE 10UL + +/** + * @brief Milliseconds to wait for the self test phase to succeed before we force reset. + */ +#define otaconfigSELF_TEST_RESPONSE_WAIT_MS 16000U + +/** + * @brief Milliseconds to wait before requesting data blocks from the OTA service if nothing is happening. + * + * The wait timer is reset whenever a data block is received from the OTA service so we will only send + * the request message after being idle for this amount of time. + */ +#define otaconfigFILE_REQUEST_WAIT_MS 10000U + +/** + * @brief The OTA agent task priority. Normally it runs at a low priority. + */ +#define otaconfigAGENT_PRIORITY tskIDLE_PRIORITY + 5U + +/** + * @brief The maximum allowed length of the thing name used by the OTA agent. + * + * AWS IoT requires Thing names to be unique for each device that connects to the broker. + * Likewise, the OTA agent requires the developer to construct and pass in the Thing name when + * initializing the OTA agent. The agent uses this size to allocate static storage for the + * Thing name used in all OTA base topics. Namely $aws/things/ + */ +#define otaconfigMAX_THINGNAME_LEN 64U + +/** + * @brief The maximum number of data blocks requested from OTA streaming service. + * + * This configuration parameter is sent with data requests and represents the maximum number of + * data blocks the service will send in response. The maximum limit for this must be calculated + * from the maximum data response limit (128 KB from service) divided by the block size. + * For example if block size is set as 1 KB then the maximum number of data blocks that we can + * request is 128/1 = 128 blocks. Configure this parameter to this maximum limit or lower based on + * how many data blocks response is expected for each data requests. + * Please note that this must be set larger than zero. + * + */ +#define otaconfigMAX_NUM_BLOCKS_REQUEST 1U + +/** + * @brief The maximum number of requests allowed to send without a response before we abort. + * + * This configuration parameter sets the maximum number of times the requests are made over + * the selected communication channel before aborting and returning error. + * + */ +#define otaconfigMAX_NUM_REQUEST_MOMENTUM 32U + +/** + * @brief The number of data buffers reserved by the OTA agent. + * + * This configurations parameter sets the maximum number of static data buffers used by + * the OTA agent for job and file data blocks received. + */ +#define otaconfigMAX_NUM_OTA_DATA_BUFFERS 1U + +/** + * @brief Allow update to same or lower version. + * + * Set this to 1 to allow downgrade or same version update.This configurations parameter + * disables version check and allows update to a same or lower version.This is provided for + * testing purpose and it is recommended to always update to higher version and keep this + * configuration disabled. + */ +#define otaconfigAllowDowngrade 0U + +/** + * @brief The protocol selected for OTA control operations. + * + * This configurations parameter sets the default protocol for all the OTA control + * operations like requesting OTA job, updating the job status etc. + * + * Note - Only MQTT is supported at this time for control operations. + */ +#define configENABLED_CONTROL_PROTOCOL ( OTA_CONTROL_OVER_MQTT ) + +/** + * @brief The protocol selected for OTA data operations. + * + * This configurations parameter sets the protocols selected for the data operations + * like requesting file blocks from the service. + * + * Note - Both MQTT and HTTP is supported for data transfer. This configuration parameter + * can be set to following - + * Enable data over MQTT - ( OTA_DATA_OVER_MQTT ) + * Enable data over HTTP - ( OTA_DATA_OVER_HTTP) + * Enable data over both MQTT & HTTP ( OTA_DATA_OVER_MQTT | OTA_DATA_OVER_HTTP ) + */ +#define configENABLED_DATA_PROTOCOLS ( OTA_DATA_OVER_MQTT ) + +/** + * @brief The preferred protocol selected for OTA data operations. + * + * Primary data protocol will be the protocol used for downloading file if more than + * one protocol is selected while creating OTA job. Default primary data protocol is MQTT + * and following update here to switch to HTTP as primary. + * + * Note - use OTA_DATA_OVER_HTTP for HTTP as primary data protocol. + */ + +#define configOTA_PRIMARY_DATA_PROTOCOL ( OTA_DATA_OVER_MQTT ) + +#endif /* _OTA_CONFIG_H_ */ diff --git a/platform/posix/ota_pal/utest/ota_pal_posix_utest.c b/platform/posix/ota_pal/utest/ota_pal_posix_utest.c new file mode 100644 index 0000000000..a934ca111d --- /dev/null +++ b/platform/posix/ota_pal/utest/ota_pal_posix_utest.c @@ -0,0 +1,757 @@ +/* + * FreeRTOS OTA V1.2.0 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + + +/** + * @file ota_jobParsing_utest.c + * @brief Unit tests for job parsing functions in ota.c + */ + +#include +#include + +#include +#include +#include "unity.h" + +/* For accessing OTA private functions. */ +#include "ota_private.h" +#include "ota_pal_posix.h" + +/* Unit test config. */ +#include "ota_utest_config.h" + +/* For the otaPal_WriteBlock_WriteManyBlocks test this is the number of blocks of + * dummyData to write to the non-volatile memory. */ +#define testotapalNUM_WRITE_BLOCKS 10 + +/* For the otaPal_WriteBlock_WriteManyBlocks test this the delay time in ms following + * the block write loop. */ +#define testotapalWRITE_BLOCKS_DELAY_MS 5000 + +/** + * @brief Invalid signature for OTA PAL testing. + */ +static const uint8_t ucInvalidSignature[] = +{ + 0x30, 0x44, 0x02, 0x20, 0x75, 0xde, 0xa8, 0x1f, 0xca, 0xec, 0xff, 0x16, + 0xbb, 0x38, 0x4b, 0xe3, 0x14, 0xe7, 0xfb, 0x68, 0xf5, 0x3e, 0x86, 0xa2, + 0x71, 0xba, 0x9e, 0x5e, 0x50, 0xbf, 0xb2, 0x7a, 0x9e, 0x00, 0xc6, 0x4d, + 0x02, 0x20, 0x19, 0x72, 0x42, 0x85, 0x2a, 0xac, 0xdf, 0x5a, 0x5e, 0xfa, + 0xad, 0x49, 0x17, 0x5b, 0xce, 0x5b, 0x65, 0x75, 0x08, 0x47, 0x3e, 0x55, + 0xf9, 0x0e, 0xdf, 0x9e, 0x8c, 0xdc, 0x95, 0xdf, 0x63, 0xd2 +}; +static const int ucInvalidSignatureLength = 70; + +/** + * @brief Valid signature matching the test block in the OTA PAL tests. + */ +static const uint8_t ucValidSignature[] = +{ + 0x30, 0x44, 0x02, 0x20, 0x15, 0x6a, 0x68, 0x98, 0xf0, 0x4e, 0x1e, 0x12, + 0x4c, 0xc4, 0xf1, 0x05, 0x22, 0x36, 0xfd, 0xb4, 0xe5, 0x5d, 0x83, 0x08, + 0x2a, 0xf3, 0xa6, 0x7d, 0x32, 0x6b, 0xff, 0x85, 0x27, 0x14, 0x9b, 0xbf, + 0x02, 0x20, 0x26, 0x7d, 0x5f, 0x4d, 0x12, 0xab, 0xec, 0x17, 0xd8, 0x45, + 0xc6, 0x3d, 0x8e, 0xd8, 0x8d, 0x3f, 0x28, 0x26, 0xfd, 0xce, 0x32, 0x34, + 0x17, 0x05, 0x47, 0xb2, 0xf6, 0x84, 0xd5, 0x68, 0x3e, 0x36 +}; +static const int ucValidSignatureLength = 70; + +/** + * @brief The type of signature method this file defines for the valid signature. + */ +#define otatestSIG_METHOD otatestSIG_SHA256_ECDSA + +/* + * @brief: This dummy data is prepended by a SHA1 hash generated from the rsa-sha1-signer + * certificate and keys in tests/common/ota/test_files. + * + * The RSA SHA256 signature and ECDSA 256 signature are generated from this entire data + * block as is. + */ +static uint8_t dummyData[] = +{ + 0x83, 0x0b, 0xf0, 0x6a, 0x81, 0xd6, 0xca, 0xd7, 0x08, 0x22, 0x0d, 0x6a, + 0x33, 0xfa, 0x31, 0x9f, 0xa9, 0x5f, 0xb5, 0x26, 0x00, 0x01, 0x02, 0x03, + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0c, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, + 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f +}; + +/* Global static OTA file context used in every test. This context is reset to all zeros + * before every test. */ +static OtaFileContext_t otaFile; + +/* ============================ UNITY FIXTURES ============================ */ + +void setUp( void ) +{ + /* Always reset the OTA file context before each test. */ + memset( &otaFile, 0, sizeof( otaFile ) ); +} + +void tearDown( void ) +{ + OtaPalMainStatus_t result; + + /* We want to abort the OTA file after every test. This closes the OtaFile. */ + result = otaPal_Abort( &otaFile ); + TEST_ASSERT_EQUAL( OtaPalSuccess, result ); + + unlink( "PlatformImageState.txt" ); +} + +/* ========================================================================== */ + +/** + * @brief Test that otaPal_Abort will return correct result code. + */ +void test_OTAPAL_Abort_NullFileContext( void ) +{ + OtaPalMainStatus_t result; + + result = OTA_PAL_MAIN_ERR( otaPal_Abort( NULL ) ); + + TEST_ASSERT_EQUAL( OtaPalFileAbort, result ); +} + + +/** + * @brief Test that otaPal_Abort will return correct result code. + */ +void test_OTAPAL_Abort_NullFileHandle( void ) +{ + OtaPalMainStatus_t result; + + otaFile.pFile = NULL; + result = OTA_PAL_MAIN_ERR( otaPal_Abort( &otaFile ) ); + + TEST_ASSERT_EQUAL( OtaPalSuccess, result ); +} + +/** + * @brief Test that otaPal_Abort will return correct result code. + */ +void test_OTAPAL_Abort_ValidFileHandle( void ) +{ + OtaPalMainStatus_t result; + + otaFile.pFilePath = ( uint8_t * ) OTA_PAL_UTEST_FIRMWARE_FILE; + result = OTA_PAL_MAIN_ERR( otaPal_CreateFileForRx( &otaFile ) ); + TEST_ASSERT_EQUAL( OtaPalSuccess, result ); + + result = OTA_PAL_MAIN_ERR( otaPal_Abort( &otaFile ) ); + TEST_ASSERT_EQUAL( OtaPalSuccess, result ); +} + +/** + * @brief Test that otaPal_Abort will return correct result code. + */ +void test_OTAPAL_Abort_NonExistentFile( void ) +{ + OtaPalMainStatus_t result; + + otaFile.pFilePath = ( uint8_t * ) ( "nonexistingfile.bin" ); + result = OTA_PAL_MAIN_ERR( otaPal_Abort( &otaFile ) ); + TEST_ASSERT_EQUAL( OtaPalSuccess, result ); +} + +/** + * @brief Test that otaPal_CreateFileForRx will return correct result code. + */ +void test_OTAPAL_CreateFileForRx_NullFileContext( void ) +{ + OtaPalMainStatus_t result; + + result = OTA_PAL_MAIN_ERR( otaPal_CreateFileForRx( NULL ) ); + TEST_ASSERT_EQUAL( OtaPalRxFileCreateFailed, result ); +} + + +/** + * @brief Test that otaPal_CreateFileForRx will return correct result code. + */ +void test_OTAPAL_CreateFileForRx_NullFilePath( void ) +{ + OtaPalMainStatus_t result; + + otaFile.pFilePath = NULL; + result = OTA_PAL_MAIN_ERR( otaPal_CreateFileForRx( &otaFile ) ); + + TEST_ASSERT_EQUAL( OtaPalRxFileCreateFailed, result ); +} + +/** + * @brief Test that otaPal_CreateFileForRx will return correct result code. + */ +void test_OTAPAL_CreateFileForRx_FailedToCreateFile( void ) +{ + OtaPalMainStatus_t result; + + chmod( OTA_PAL_UTEST_FIRMWARE_FILE, S_IRUSR ); + otaFile.pFilePath = ( uint8_t * ) OTA_PAL_UTEST_FIRMWARE_FILE; + + /* Create a file that exists with w+b mode */ + result = OTA_PAL_MAIN_ERR( otaPal_CreateFileForRx( &otaFile ) ); + TEST_ASSERT_EQUAL( OtaPalRxFileCreateFailed, result ); + + chmod( OTA_PAL_UTEST_FIRMWARE_FILE, S_IRWXU ); + result = OTA_PAL_MAIN_ERR( otaPal_CreateFileForRx( &otaFile ) ); + TEST_ASSERT_EQUAL( OtaPalSuccess, result ); +} + +/** + * @brief Test that otaPal_CreateFileForRx will return correct result code. + */ +void test_OTAPAL_CreateFileForRx_ValidFileHandle( void ) +{ + OtaPalMainStatus_t result; + + otaFile.pFilePath = ( uint8_t * ) OTA_PAL_UTEST_FIRMWARE_FILE; + result = OTA_PAL_MAIN_ERR( otaPal_CreateFileForRx( &otaFile ) ); + TEST_ASSERT_EQUAL( OtaPalSuccess, result ); +} + + +/** + * @brief Test that otaPal_WriteBlock will return correct result code. + */ +void test_OTAPAL_WriteBlock_NullFileContext( void ) +{ + int16_t bytes_written = 0; + uint8_t data = 0xAA; + + bytes_written = otaPal_WriteBlock( NULL, 0, &data, 1 ); + TEST_ASSERT_EQUAL( OtaPalSuccess, bytes_written + 1 ); +} + + +/** + * @brief Test that otaPal_WriteBlock will return correct result code. + */ +void test_OTAPAL_WriteBlock_WriteSingleByte( void ) +{ + OtaPalMainStatus_t result; + int16_t numBytesWritten; + uint8_t data = 0xAA; + + /* TEST: Write a byte of data. */ + otaFile.pFilePath = ( uint8_t * ) OTA_PAL_UTEST_FIRMWARE_FILE; + result = OTA_PAL_MAIN_ERR( otaPal_CreateFileForRx( &otaFile ) ); + TEST_ASSERT_EQUAL( OtaPalSuccess, result ); + + if( TEST_PROTECT() ) + { + numBytesWritten = otaPal_WriteBlock( &otaFile, 0, &data, 1 ); + TEST_ASSERT_EQUAL_INT( 1, numBytesWritten ); + } +} + +/** + * @brief Test that otaPal_WriteBlock will return correct result code. + */ +void test_OTAPAL_WriteBlock_WriteManyBlocks( void ) +{ + OtaPalMainStatus_t result; + int16_t numBytesWritten; + + otaFile.pFilePath = ( uint8_t * ) OTA_PAL_UTEST_FIRMWARE_FILE; + otaFile.fileSize = sizeof( dummyData ) * testotapalNUM_WRITE_BLOCKS; + /* TEST: Write many bytes of data. */ + + otaFile.pFilePath = ( uint8_t * ) OTA_PAL_UTEST_FIRMWARE_FILE; + result = OTA_PAL_MAIN_ERR( otaPal_CreateFileForRx( &otaFile ) ); + TEST_ASSERT_EQUAL( OtaPalSuccess, result ); + + if( TEST_PROTECT() ) + { + int index = 0; + + for( index = 0; index < testotapalNUM_WRITE_BLOCKS; index++ ) + { + numBytesWritten = otaPal_WriteBlock( &otaFile, index * sizeof( dummyData ), dummyData, sizeof( dummyData ) ); + TEST_ASSERT_EQUAL_INT( sizeof( dummyData ), numBytesWritten ); + } + + /* Sufficient delay for flash write to complete. */ + /* vTaskDelay( pdMS_TO_TICKS( testotapalWRITE_BLOCKS_DELAY_MS ) ); */ + } +} + +/** + * @brief Test that otaPal_WriteBlock will return correct result code. + */ +void test_OTAPAL_WriteBlock_FseekError( void ) +{ +} + +/** + * @brief Test that otaPal_WriteBlock will return correct result code. + */ +void test_OTAPAL_WriteBlock_FwriteError( void ) +{ +} + +void test_OTAPAL_CloseFile_ValidSignature( void ) +{ + OtaPalMainStatus_t result; + int16_t bytes_written = 0; + Sig256_t sig = { 0 }; + + /* We use a dummy file name here because closing the system designated bootable + * image with content that is not runnable may cause issues. */ + otaFile.pFilePath = ( uint8_t * ) ( "test_happy_path_image.bin" ); + otaFile.fileSize = sizeof( dummyData ); + result = OTA_PAL_MAIN_ERR( otaPal_CreateFileForRx( &otaFile ) ); + TEST_ASSERT_EQUAL( OtaPalSuccess, result ); + + /* We still want to close the file if the test fails somewhere here. */ + if( TEST_PROTECT() ) + { + /* Write data to the file. */ + bytes_written = otaPal_WriteBlock( &otaFile, + 0, + dummyData, + sizeof( dummyData ) ); + TEST_ASSERT_EQUAL( sizeof( dummyData ), bytes_written ); + + otaFile.pSignature = &sig; + otaFile.pSignature->size = ucValidSignatureLength; + memcpy( otaFile.pSignature->data, ucValidSignature, ucValidSignatureLength ); + otaFile.pCertFilepath = ( uint8_t * ) OTA_PAL_UTEST_CERT_FILE; + + result = OTA_PAL_MAIN_ERR( otaPal_CloseFile( &otaFile ) ); + TEST_ASSERT_EQUAL_INT( OtaPalSuccess, result ); + } +} + + +/** + * @brief Call otaPal_CloseFile with an invalid signature in the file context. + * The close is called after we have a written a block of dummy data to the file. + * Verify the correct OTA Agent level error code is returned from otaPal_CloseFile. + */ +void test_OTAPAL_CloseFile_InvalidSignatureBlockWritten( void ) +{ + OtaPalMainStatus_t result; + int16_t bytes_written = 0; + Sig256_t sig = { 0 }; + + /* Create a local file using the PAL. */ + otaFile.pFilePath = ( uint8_t * ) OTA_PAL_UTEST_FIRMWARE_FILE; + otaFile.fileSize = sizeof( dummyData ); + + result = OTA_PAL_MAIN_ERR( otaPal_CreateFileForRx( &otaFile ) ); + TEST_ASSERT_EQUAL( OtaPalSuccess, result ); + + /* We still want to close the file if the test fails somewhere here. */ + if( TEST_PROTECT() ) + { + /* Write data to the file. */ + bytes_written = otaPal_WriteBlock( &otaFile, + 0, + dummyData, + sizeof( dummyData ) ); + TEST_ASSERT_EQUAL( sizeof( dummyData ), bytes_written ); + + /* Fill out an incorrect signature. */ + otaFile.pSignature = &sig; + otaFile.pSignature->size = ucInvalidSignatureLength; + memcpy( otaFile.pSignature->data, ucInvalidSignature, ucInvalidSignatureLength ); + otaFile.pCertFilepath = ( uint8_t * ) OTA_PAL_UTEST_CERT_FILE; + + /* Try to close the file. */ + result = OTA_PAL_MAIN_ERR( otaPal_CloseFile( &otaFile ) ); + + if( ( OtaPalBadSignerCert != result ) && + ( OtaPalSignatureCheckFailed != result ) && + ( OtaPalFileClose != result ) ) + { + TEST_ASSERT_TRUE( 0 ); + } + } +} + +/** + * @brief Call otaPal_CloseFile with an invalid signature in the file context. + * The close is called when no blocks have been written to the file. + * Verify the correct OTA Agent level error code is returned from otaPal_CloseFile. + */ +void test_OTAPAL_CloseFile_InvalidSignatureNoBlockWritten( void ) +{ + OtaPalMainStatus_t result; + Sig256_t sig = { 0 }; + + /* Create a local file using the PAL. */ + otaFile.pFilePath = ( uint8_t * ) OTA_PAL_UTEST_FIRMWARE_FILE; + result = OTA_PAL_MAIN_ERR( otaPal_CreateFileForRx( &otaFile ) ); + TEST_ASSERT_EQUAL( OtaPalSuccess, result ); + + /* Fill out an incorrect signature. */ + otaFile.pSignature = &sig; + otaFile.pSignature->size = ucInvalidSignatureLength; + memcpy( otaFile.pSignature->data, ucInvalidSignature, ucInvalidSignatureLength ); + otaFile.pCertFilepath = ( uint8_t * ) OTA_PAL_UTEST_CERT_FILE; + + /* We still want to close the file if the test fails somewhere here. */ + if( TEST_PROTECT() ) + { + /* Try to close the file. */ + result = OTA_PAL_MAIN_ERR( otaPal_CloseFile( &otaFile ) ); + + if( ( OtaPalBadSignerCert != result ) && + ( OtaPalSignatureCheckFailed != result ) && + ( OtaPalFileClose != result ) ) + { + TEST_ASSERT_TRUE( 0 ); + } + } +} + +/** + * @brief Call otaPal_CloseFile with a signature verification certificate path does + * not exist in the system. Verify the correct OTA Agent level error code is returned + * from otaPal_CloseFile. + * + * @note This test is only valid if your device uses a file system in your non-volatile memory. + * Some devices may revert to using aws_codesigner_certificate.h if a file is not found, but + * that option is not being enforced. + */ +void test_OTAPAL_CloseFile_NonexistingCodeSignerCertificate( void ) +{ + OtaPalMainStatus_t result; + int16_t bytes_written = 0; + Sig256_t sig = { 0 }; + + memset( &otaFile, 0, sizeof( otaFile ) ); + + /* Create a local file using the PAL. */ + otaFile.pFilePath = ( uint8_t * ) OTA_PAL_UTEST_FIRMWARE_FILE; + otaFile.fileSize = sizeof( dummyData ); + + result = OTA_PAL_MAIN_ERR( otaPal_CreateFileForRx( &otaFile ) ); + TEST_ASSERT_EQUAL( OtaPalSuccess, result ); + + /* We still want to close the file if the test fails somewhere here. */ + if( TEST_PROTECT() ) + { + /* Write data to the file. */ + bytes_written = otaPal_WriteBlock( &otaFile, + 0, + dummyData, + sizeof( dummyData ) ); + TEST_ASSERT_EQUAL( sizeof( dummyData ), bytes_written ); + + /* Check the signature (not expected to be valid in this case). */ + otaFile.pSignature = &sig; + otaFile.pSignature->size = ucValidSignatureLength; + memcpy( otaFile.pSignature->data, ucValidSignature, ucValidSignatureLength ); + otaFile.pCertFilepath = ( uint8_t * ) ( "nonexistingfile.crt" ); + + result = OTA_PAL_MAIN_ERR( otaPal_CloseFile( &otaFile ) ); + + if( ( OtaPalBadSignerCert != result ) && + ( OtaPalSignatureCheckFailed != result ) && + ( OtaPalFileClose != result ) ) + { + TEST_ASSERT_TRUE( 0 ); + } + } +} + +/** + * @brief Test that otaPal_ResetDevice will return correct result code. + */ +void test_OTAPAL_ResetDevice_NullFileContext( void ) +{ + OtaPalMainStatus_t result; + + /* Currently there is nothing done inside the function. It's a placeholder. */ + result = OTA_PAL_MAIN_ERR( otaPal_ResetDevice( NULL ) ); + TEST_ASSERT_EQUAL( OtaPalSuccess, result ); +} + +/** + * @brief Test that otaPal_WriteBlock will return correct result code. + */ +void test_OTAPAL_ActivateNewImage_NullFileContext( void ) +{ + OtaPalMainStatus_t result; + + result = OTA_PAL_MAIN_ERR( otaPal_ActivateNewImage( NULL ) ); + TEST_ASSERT_EQUAL( OtaPalSuccess, result ); +} + +/** + * @brief Set the platform state to self-test and verify success. + */ +void test_OTAPAL_SetPlatformImageState_SelfTestImageState( void ) +{ + OtaPalMainStatus_t result; + int16_t bytes_written = 0; + + OtaImageState_t eImageState = OtaImageStateUnknown; + OtaPalImageState_t palImageState = OtaPalImageStateUnknown; + + /* Create a local file again using the PAL. */ + otaFile.pFilePath = ( uint8_t * ) OTA_PAL_UTEST_FIRMWARE_FILE; + otaFile.fileSize = sizeof( dummyData ); + + result = OTA_PAL_MAIN_ERR( otaPal_CreateFileForRx( &otaFile ) ); + TEST_ASSERT_EQUAL( OtaPalSuccess, result ); + + /* We still want to close the file if the test fails. */ + if( TEST_PROTECT() ) + { + /* Write data to the file. */ + bytes_written = otaPal_WriteBlock( &otaFile, + 0, + dummyData, + sizeof( dummyData ) ); + TEST_ASSERT_EQUAL( sizeof( dummyData ), bytes_written ); + + /* Set the image state. */ + eImageState = OtaImageStateTesting; + result = OTA_PAL_MAIN_ERR( otaPal_SetPlatformImageState( &otaFile, eImageState ) ); + TEST_ASSERT_EQUAL_INT( OtaPalSuccess, result ); + + /* Verify that image state was saved correctly. */ + + /* [**]All platforms need a reboot of a successfully close image in order to return + * eOTA_PAL_ImageState_PendingCommit from otaPal_GetPlatformImageState(). So this cannot be tested. + */ + /* For Linux platform, this can be read directly from the image state file */ + palImageState = otaPal_GetPlatformImageState( &otaFile ); + TEST_ASSERT_EQUAL_INT( OtaPalImageStatePendingCommit, palImageState ); + } +} + +/** + * @brief Set an invalid platform image state exceeding the range and verify success. + */ +void test_OTAPAL_SetPlatformImageState_InvalidImageState( void ) +{ + OtaPalMainStatus_t result; + int16_t bytes_written = 0; + OtaImageState_t eImageState = OtaImageStateUnknown; + OtaPalImageState_t palImageState = OtaPalImageStateUnknown; + + /* Create a local file again using the PAL. */ + otaFile.pFilePath = ( uint8_t * ) OTA_PAL_UTEST_FIRMWARE_FILE; + otaFile.fileSize = sizeof( dummyData ); + + result = OTA_PAL_MAIN_ERR( otaPal_CreateFileForRx( &otaFile ) ); + TEST_ASSERT_EQUAL( OtaPalSuccess, result ); + + /* We still want to close the file if the test fails. */ + if( TEST_PROTECT() ) + { + /* Write data to the file. */ + bytes_written = otaPal_WriteBlock( &otaFile, + 0, + dummyData, + sizeof( dummyData ) ); + TEST_ASSERT_EQUAL( sizeof( dummyData ), bytes_written ); + + /* Try to set an invalid image state. */ + eImageState = ( OtaImageState_t ) ( OtaLastImageState + 1 ); + result = OTA_PAL_MAIN_ERR( otaPal_SetPlatformImageState( &otaFile, eImageState ) ); + TEST_ASSERT_EQUAL( OtaPalBadImageState, result ); + + /* Read the platform image state to verify */ + /* Nothing wrote to the image state file. Ota Pal Image state remain valid */ + palImageState = otaPal_GetPlatformImageState( &otaFile ); + TEST_ASSERT_EQUAL_INT( OtaPalImageStateValid, palImageState ); + } +} + +/** + * @brief Set the image state to unknown and verify a failure. + */ +void test_OTAPAL_SetPlatformImageState_UnknownImageState( void ) +{ + OtaPalMainStatus_t result; + int16_t bytes_written = 0; + OtaImageState_t eImageState = OtaImageStateUnknown; + OtaPalImageState_t palImageState = OtaPalImageStateUnknown; + + /* Create a local file again using the PAL. */ + otaFile.pFilePath = ( uint8_t * ) OTA_PAL_UTEST_FIRMWARE_FILE; + otaFile.fileSize = sizeof( dummyData ); + + result = OTA_PAL_MAIN_ERR( otaPal_CreateFileForRx( &otaFile ) ); + TEST_ASSERT_EQUAL( OtaPalSuccess, result ); + + /* We still want to close the file if the test fails. */ + if( TEST_PROTECT() ) + { + /* Write data to the file. */ + bytes_written = otaPal_WriteBlock( &otaFile, + 0, + dummyData, + sizeof( dummyData ) ); + TEST_ASSERT_EQUAL( sizeof( dummyData ), bytes_written ); + + /* Try to set an invalid image state. */ + eImageState = OtaImageStateUnknown; + result = OTA_PAL_MAIN_ERR( otaPal_SetPlatformImageState( &otaFile, eImageState ) ); + TEST_ASSERT_EQUAL( OtaPalBadImageState, result ); + + /* Read the platform image state to verify */ + /* Nothing wrote to the image state file. Ota Pal Image state remain valid */ + palImageState = otaPal_GetPlatformImageState( &otaFile ); + TEST_ASSERT_EQUAL_INT( OtaPalImageStateValid, palImageState ); + } +} + + +/** + * @brief Set platform image state to rejected and verify success. + * We cannot test a reject failed without mocking the underlying non volatile memory. + */ +void test_OTAPAL_SetPlatformImageState_RejectImageState( void ) +{ + OtaPalMainStatus_t result; + int16_t bytes_written = 0; + OtaImageState_t eImageState = OtaImageStateUnknown; + OtaPalImageState_t palImageState = OtaPalImageStateUnknown; + + /* Create a local file again using the PAL. */ + otaFile.pFilePath = ( uint8_t * ) OTA_PAL_UTEST_FIRMWARE_FILE; + otaFile.fileSize = sizeof( dummyData ); + + result = OTA_PAL_MAIN_ERR( otaPal_CreateFileForRx( &otaFile ) ); + TEST_ASSERT_EQUAL( OtaPalSuccess, result ); + + /* We still want to close the file if the test fails. */ + if( TEST_PROTECT() ) + { + /* Write data to the file. */ + bytes_written = otaPal_WriteBlock( &otaFile, + 0, + dummyData, + sizeof( dummyData ) ); + TEST_ASSERT_EQUAL( sizeof( dummyData ), bytes_written ); + + eImageState = OtaImageStateRejected; + result = OTA_PAL_MAIN_ERR( otaPal_SetPlatformImageState( &otaFile, eImageState ) ); + TEST_ASSERT_EQUAL_INT( OtaPalSuccess, result ); + + /* Read the platform image state to verify */ + palImageState = otaPal_GetPlatformImageState( &otaFile ); + TEST_ASSERT_EQUAL_INT( OtaPalImageStateInvalid, palImageState ); + } +} + +/** + * @brief Set the platform image state to aborted. + * We cannot test a abort failed without mocking the underlying non-volatile memory. + */ +void test_OTAPAL_SetPlatformImageState_AbortImageState( void ) +{ + OtaPalMainStatus_t result; + int16_t bytes_written = 0; + OtaImageState_t eImageState = OtaImageStateUnknown; + OtaPalImageState_t palImageState = OtaPalImageStateUnknown; + + /* Create a local file again using the PAL. */ + otaFile.pFilePath = ( uint8_t * ) OTA_PAL_UTEST_FIRMWARE_FILE; + otaFile.fileSize = sizeof( dummyData ); + + result = OTA_PAL_MAIN_ERR( otaPal_CreateFileForRx( &otaFile ) ); + TEST_ASSERT_EQUAL( OtaPalSuccess, result ); + + /* We still want to close the file if the test fails. */ + if( TEST_PROTECT() ) + { + /* Write data to the file. */ + bytes_written = otaPal_WriteBlock( &otaFile, + 0, + dummyData, + sizeof( dummyData ) ); + TEST_ASSERT_EQUAL( sizeof( dummyData ), bytes_written ); + + eImageState = OtaImageStateAborted; + result = OTA_PAL_MAIN_ERR( otaPal_SetPlatformImageState( &otaFile, eImageState ) ); + TEST_ASSERT_EQUAL_INT( OtaPalSuccess, result ); + + /* Read the platform image state to verify */ + palImageState = otaPal_GetPlatformImageState( &otaFile ); + TEST_ASSERT_EQUAL_INT( OtaPalImageStateInvalid, palImageState ); + } +} + +/** + * @brief Verify that the current image received is in the invalid state after a + * failure to close the file because of a bad signature. + */ +void test_OTAPAL_GetPlatformImageState_InvalidImageStateFromFileCloseFailure( void ) +{ + OtaPalMainStatus_t result; + int16_t bytes_written = 0; + Sig256_t sig = { 0 }; + OtaPalImageState_t ePalImageState = OtaPalImageStateUnknown; + + /* TEST: Invalid image returned from otaPal_GetPlatformImageState(). Using a failure to close. */ + /* Create a local file again using the PAL. */ + otaFile.pFilePath = ( uint8_t * ) OTA_PAL_UTEST_FIRMWARE_FILE; + otaFile.fileSize = sizeof( dummyData ); + + result = OTA_PAL_MAIN_ERR( otaPal_CreateFileForRx( &otaFile ) ); + TEST_ASSERT_EQUAL( OtaPalSuccess, result ); + + /* We still want to close the file if the test fails. */ + if( TEST_PROTECT() ) + { + /* Write data to the file. */ + bytes_written = otaPal_WriteBlock( &otaFile, + 0, + dummyData, + sizeof( dummyData ) ); + TEST_ASSERT_EQUAL( sizeof( dummyData ), bytes_written ); + + /* Check the signature. */ + otaFile.pSignature = &sig; + otaFile.pSignature->size = ucInvalidSignatureLength; + memcpy( otaFile.pSignature->data, ucInvalidSignature, ucInvalidSignatureLength ); + otaFile.pCertFilepath = ( uint8_t * ) OTA_PAL_UTEST_CERT_FILE; + + result = OTA_PAL_MAIN_ERR( otaPal_CloseFile( &otaFile ) ); + + if( ( OtaPalBadSignerCert != result ) && + ( OtaPalSignatureCheckFailed != result ) && + ( OtaPalFileClose != result ) ) + { + TEST_ASSERT_TRUE( 0 ); + } + + /* The file failed to close, so it is invalid or in an unknown state. */ + ePalImageState = otaPal_GetPlatformImageState( &otaFile ); + TEST_ASSERT( ( OtaPalImageStateInvalid == ePalImageState ) || + ( OtaPalImageStateUnknown == ePalImageState ) ); + } +} diff --git a/platform/posix/ota_pal/utest/ota_utest_config.h.in b/platform/posix/ota_pal/utest/ota_utest_config.h.in new file mode 100644 index 0000000000..33318b8cc7 --- /dev/null +++ b/platform/posix/ota_pal/utest/ota_utest_config.h.in @@ -0,0 +1,44 @@ +/* + * AWS IoT Device SDK for Embedded C V202011.00 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file ota_config.h + * @brief OTA user configurable settings. + */ + +#ifndef _OTA_UTEST_CONFIG_H_ +#define _OTA_UTEST_CONFIG_H_ + +/** + * @brief Path to cert for OTA PAL test. Used to verify signature. + */ +#cmakedefine OTA_PAL_UTEST_CERT_FILE "@OTA_PAL_UTEST_CERT_FILE@" + +/** + * @brief Some devices have a hard-coded name for the firmware image to boot. + */ +#cmakedefine OTA_PAL_UTEST_FIRMWARE_FILE "@OTA_PAL_UTEST_FIRMWARE_FILE@" + +#endif diff --git a/platform/posix/ota_pal/utest/test_files/ecdsa-sha256-signer.crt.pem b/platform/posix/ota_pal/utest/test_files/ecdsa-sha256-signer.crt.pem new file mode 100644 index 0000000000..7c33a6256b --- /dev/null +++ b/platform/posix/ota_pal/utest/test_files/ecdsa-sha256-signer.crt.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBXDCCAQOgAwIBAgIJAPMhJT8l0C6AMAoGCCqGSM49BAMCMCExHzAdBgNVBAMM +FnRlc3Rfc2lnbmVyQGFtYXpvbi5jb20wHhcNMTgwNjI3MjAwNDQyWhcNMTkwNjI3 +MjAwNDQyWjAhMR8wHQYDVQQDDBZ0ZXN0X3NpZ25lckBhbWF6b24uY29tMFkwEwYH +KoZIzj0CAQYIKoZIzj0DAQcDQgAEyza/tGLVbVxhL41iYtC8D6tGEvAHu498gNtq +DtPsKaoR3t5xQx+6zdWiCi32fgFT2vkeVAmX3pf/Gl8nIP48ZqMkMCIwCwYDVR0P +BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMAoGCCqGSM49BAMCA0cAMEQCIDkf +83Oq8sOXhSyJCWAN63gc4vp9//RFCXh/hUXPYcTWAiBgmQ5JV2MZH01Upi2lMflN +YLbC+lYscwcSlB2tECUbJA== +-----END CERTIFICATE-----