From 5ababf911d07027ae9357b86adbd104d59d40c4f Mon Sep 17 00:00:00 2001 From: Frank Voorburg Date: Fri, 19 Oct 2018 20:22:27 +0000 Subject: [PATCH] Refs #618. Added support for splitting the XCP seed/key over multiple packets to make larger seed/key values possible, specifically on CAN. git-svn-id: https://svn.code.sf.net/p/openblt/code/trunk@630 5dc33758-31d5-4daf-9ae8-b24bf3d40d73 --- Host/Source/LibOpenBLT/xcploader.c | 132 ++++++++++----- Host/libopenblt.dll | Bin 165376 -> 165888 bytes Target/Source/plausibility.h | 15 ++ Target/Source/xcp.c | 247 ++++++++++++++++++++--------- Target/Source/xcp.h | 9 +- 5 files changed, 283 insertions(+), 120 deletions(-) diff --git a/Host/Source/LibOpenBLT/xcploader.c b/Host/Source/LibOpenBLT/xcploader.c index bf3a82e4..42879168 100644 --- a/Host/Source/LibOpenBLT/xcploader.c +++ b/Host/Source/LibOpenBLT/xcploader.c @@ -81,7 +81,7 @@ static uint16_t XcpLoaderGetOrderedWord(uint8_t const * data); static bool XcpLoaderSendCmdConnect(void); static bool XcpLoaderSendCmdGetStatus(uint8_t * session, uint8_t * protectedResources, uint16_t * configId); -static bool XcpLoaderSendCmdGetSeed(uint8_t resource, uint8_t * seed, uint8_t * seedLen); +static bool XcpLoaderSendCmdGetSeed(uint8_t resource, uint8_t mode, uint8_t * seed, uint8_t * seedLen); static bool XcpLoaderSendCmdUnlock(uint8_t const * key, uint8_t keyLen, uint8_t * protectedResources); static bool XcpLoaderSendCmdSetMta(uint32_t address); @@ -316,10 +316,16 @@ static bool XcpLoaderStart(void) if ( (protectedResources & XCPPROTECT_RESOURCE_PGM) != 0) { uint8_t availableResources = 0; - uint8_t seed[XCPLOADER_PACKET_SIZE_MAX-2] = { 0 }; - uint8_t seedLen = 0; - uint8_t key[XCPLOADER_PACKET_SIZE_MAX-2] = { 0 }; - uint8_t keyLen = 0; + uint8_t seed[256] = { 0 }; + uint8_t *seedPtr = &seed[0]; + uint8_t seedTotalLen = 0; + uint8_t seedRemainingLen = 0; + uint8_t key[256] = { 0 }; + uint8_t keyTotalLen = 0; + uint8_t keyRemainingLen = 0; + uint8_t keyCurrentLen = 0; + uint8_t *keyPtr = &key[0]; + /* Make sure the XCP protection module contains an unlock algorithm for the * programming resource. */ @@ -340,22 +346,41 @@ static bool XcpLoaderStart(void) result = false; } } - /* Request the seed for unlocking the programming resources. */ + /* Request (first part of) the seed for unlocking the programming resources. */ if (result) { - if (!XcpLoaderSendCmdGetSeed(XCPPROTECT_RESOURCE_PGM, seed, &seedLen)) + if (!XcpLoaderSendCmdGetSeed(XCPPROTECT_RESOURCE_PGM, 0, seedPtr, &seedRemainingLen)) { result = false; } + else + { + /* store the total seed length */ + seedTotalLen = seedRemainingLen; + } + } + /* Check if more parts of the seed need to be requested. */ + if (result) + { + while (seedRemainingLen > (xcpMaxDto - 2)) + { + /* update the seed pointer for the next part */ + seedPtr += (xcpMaxDto - 2); + if (!XcpLoaderSendCmdGetSeed(XCPPROTECT_RESOURCE_PGM, 1, seedPtr, &seedRemainingLen)) + { + result = false; + break; + } + } } /* Only continue with resource unlock operation if not already unlocked, which * is indicated by a seed length of 0. */ - if ( (result) && (seedLen > 0) ) + if ( (result) && (seedTotalLen > 0) ) { /* Compute the key using the XCP protection module. */ - if (!XCPProtectComputeKeyFromSeed(XCPPROTECT_RESOURCE_PGM, seedLen, seed, - &keyLen, key)) + if (!XCPProtectComputeKeyFromSeed(XCPPROTECT_RESOURCE_PGM, seedTotalLen, seed, + &keyTotalLen, key)) { result = false; } @@ -363,16 +388,38 @@ static bool XcpLoaderStart(void) if (result) { uint8_t currentlyProtectedResources = 0; - /* Send the key to unlock the resource. */ - if (!XcpLoaderSendCmdUnlock(key, keyLen, ¤tlyProtectedResources)) + + /* Initialize remaining length */ + keyRemainingLen = keyTotalLen; + + /* Send the key to unlock the resource */ + while (keyRemainingLen > 0) { - result = false; - } - /* Double-check that the programming resource is now unlocked. */ - else if ((currentlyProtectedResources & XCPPROTECT_RESOURCE_PGM) != 0) - { - /* Programming resource unlock operation failed. */ - result = false; + /* Determine how many key bytes are about to be sent. */ + keyCurrentLen = keyRemainingLen; + if (keyCurrentLen > (xcpMaxCto - 2)) + { + keyCurrentLen = (xcpMaxCto - 2); + } + /* The the (possible partial) unlock command. */ + if (!XcpLoaderSendCmdUnlock(keyPtr, keyRemainingLen, ¤tlyProtectedResources)) + { + result = false; + break; + } + /* Update key pointer and the remaining length */ + keyRemainingLen -= keyCurrentLen; + keyPtr += keyCurrentLen; + /* Check if the key was now completely sent. */ + if (keyRemainingLen == 0) + { + /* Double-check that the programming resource is now unlocked. */ + if ((currentlyProtectedResources & XCPPROTECT_RESOURCE_PGM) != 0) + { + /* Programming resource unlock operation failed. */ + result = false; + } + } } } } @@ -825,14 +872,17 @@ static bool XcpLoaderSendCmdGetStatus(uint8_t * session, uint8_t * protectedReso /************************************************************************************//** ** \brief Sends the XCP Get Seed command. ** \param resource The resource to unlock (XCPPROTECT_RESOURCE_xxx). +** \param mode 0 for the first part of the seed, 1 for the remaining part. ** \param seed Pointer to byte array where the received seed is stored. ** \param seedLen Length of the seed in bytes. ** \return True if successful, false otherwise. ** ****************************************************************************************/ -static bool XcpLoaderSendCmdGetSeed(uint8_t resource, uint8_t * seed, uint8_t * seedLen) +static bool XcpLoaderSendCmdGetSeed(uint8_t resource, uint8_t mode, uint8_t * seed, + uint8_t * seedLen) { bool result = false; + uint8_t currentSeedLen; tXcpTransportPacket cmdPacket; tXcpTransportPacket resPacket; @@ -857,12 +907,7 @@ static bool XcpLoaderSendCmdGetSeed(uint8_t resource, uint8_t * seed, uint8_t * result = true; /* Prepare the command packet. */ cmdPacket.data[0] = XCPLOADER_CMD_GET_SEED; - /* Always use mode 0 because only seeds up to 48-bit are supported currently. - * This fits in 6-bytes, making it work with the all currently supported transport - * layers. CAN is the limiting one, because the max packet length is 8-bytes for - * this transport layer. - */ - cmdPacket.data[1] = 0; + cmdPacket.data[1] = mode; cmdPacket.data[2] = resource; cmdPacket.len = 3; /* Send the packet. */ @@ -886,20 +931,18 @@ static bool XcpLoaderSendCmdGetSeed(uint8_t resource, uint8_t * seed, uint8_t * /* Extract and store the seed. */ if (result) { - /* Make sure the seed length is valid. */ - if (resPacket.data[1] > (xcpMaxCto - 2)) + /* Store the seed length. */ + *seedLen = resPacket.data[1]; + /* Determine the number of seed bytes in the current response */ + currentSeedLen = *seedLen; + if (currentSeedLen > (xcpMaxCto - 2)) { - result = false; + currentSeedLen = (xcpMaxCto - 2); } - else + /* Store the seed bytes. */ + for (uint8_t idx = 0; idx < currentSeedLen; idx++) { - /* Store the seed length. */ - *seedLen = resPacket.data[1]; - /* Store the seed. */ - for (uint8_t idx = 0; idx < *seedLen; idx++) - { - seed[idx] = resPacket.data[idx + 2]; - } + seed[idx] = resPacket.data[idx + 2]; } } } @@ -921,6 +964,7 @@ static bool XcpLoaderSendCmdUnlock(uint8_t const * key, uint8_t keyLen, uint8_t * protectedResources) { bool result = false; + uint8_t keyCurrentLen; tXcpTransportPacket cmdPacket; tXcpTransportPacket resPacket; @@ -928,23 +972,29 @@ static bool XcpLoaderSendCmdUnlock(uint8_t const * key, uint8_t keyLen, assert(xcpSettings.transport != NULL); assert(key != NULL); assert(keyLen > 0); - assert(keyLen <= (xcpMaxCto - 2)); assert(protectedResources != NULL); /* Only continue with a valid transport layer and parameters. */ if ( (xcpSettings.transport != NULL) && (key != NULL) && (keyLen > 0) && - (keyLen <= (xcpMaxCto - 2)) && (protectedResources != NULL) ) /*lint !e774 */ + (protectedResources != NULL) ) /*lint !e774 */ { /* Init the result value to okay and only set it to error when a problem occurred. */ result = true; /* Prepare the command packet. */ cmdPacket.data[0] = XCPLOADER_CMD_UNLOCK; cmdPacket.data[1] = keyLen; - for (uint8_t idx = 0; idx < keyLen; idx++) + /* Determine number of key bytes for the packet. */ + keyCurrentLen = keyLen; + if (keyCurrentLen > (xcpMaxCto - 2)) + { + keyCurrentLen = xcpMaxCto - 2; + } + /* Copy key bytes. */ + for (uint8_t idx = 0; idx < keyCurrentLen; idx++) { cmdPacket.data[idx + 2] = key[idx]; } - cmdPacket.len = keyLen + 2; + cmdPacket.len = keyCurrentLen + 2; /* Send the packet. */ if (!xcpSettings.transport->SendPacket(&cmdPacket, &resPacket, xcpSettings.timeoutT1)) diff --git a/Host/libopenblt.dll b/Host/libopenblt.dll index 4079a48b79709eaa0e5915aa2a5cc8422cf8b738..5ff3dc9fdcb3467f7ac10b4154e874398f6a7ddb 100644 GIT binary patch delta 34650 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 O@%BXf9y|i8jQC$ZPEe!( delta 34424 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 oJ50 0" +#endif + +#ifndef BOOT_XCP_KEY_MAX_LEN +#define BOOT_XCP_KEY_MAX_LEN (64) +#endif + +#if (BOOT_XCP_KEY_MAX_LEN <= 0) +#error "BOOT_XCP_KEY_MAX_LEN must be > 0" +#endif #endif /* PLAUSIBILITY_H */ /*********************************** end of plausibility.h *****************************/ diff --git a/Target/Source/xcp.c b/Target/Source/xcp.c index e4a688e5..cbaa1c09 100644 --- a/Target/Source/xcp.c +++ b/Target/Source/xcp.c @@ -915,68 +915,120 @@ static void XcpCmdBuildCheckSum(blt_int8u *data) static void XcpCmdGetSeed(blt_int8u *data) { blt_int8u resourceOK; - - /* init resource check variable as if an illegal resource is requested */ - resourceOK = 0; - - /* check if calibration/paging resource is requested for seed/key and make - * sure this is the only requested resource - */ - if (((data[2] & XCP_RES_CALPAG) > 0) && ((data[2] & ~XCP_RES_CALPAG) == 0)) - { - resourceOK = 1; - } - - /* check if programming resource is requested for seed/key and make - * sure this is the only requested resource - */ - if (((data[2] & XCP_RES_PGM) > 0) && ((data[2] & ~XCP_RES_PGM) == 0)) - { - resourceOK = 1; - } - - /* check if data acquisition resource is requested for seed/key and make - * sure this is the only requested resource - */ - if (((data[2] & XCP_RES_DAQ) > 0) && ((data[2] & ~XCP_RES_DAQ) == 0)) - { - resourceOK = 1; - } - - /* check if data stimulation resource is requested for seed/key and make - * sure this is the only requested resource - */ - if (((data[2] & XCP_RES_STIM) > 0) && ((data[2] & ~XCP_RES_STIM) == 0)) - { - resourceOK = 1; - } - - /* now process the resource validation */ - if (resourceOK == 0) - { - XcpSetCtoError(XCP_ERR_OUT_OF_RANGE); - return; - } - - /* store resource for which the seed/key sequence is started */ - xcpInfo.s_n_k_resource = data[2]; + /* made seed buffer static to lower stack load */ + static blt_int8u seedBuffer[XCP_SEED_MAX_LEN]; + static blt_int8u seedRemainderLen = 0; + static blt_int8u *seedCurrentPtr; + static blt_bool sequenceInProgress = BLT_FALSE; + blt_int8u seedCurrentLen; /* set packet id to command response packet */ xcpInfo.ctoData[0] = XCP_PID_RES; - /* request the seed from the application */ - xcpInfo.ctoData[1] = XcpGetSeed(xcpInfo.s_n_k_resource, &xcpInfo.ctoData[2]); - - /* seed cannot be longer than XCP_CTO_PACKET_LEN-2 */ - if (xcpInfo.ctoData[1] > (XCP_CTO_PACKET_LEN-2)) + /* validate requested resource in case the mode flag equals 0 */ + if (data[1] == 0) { - /* seed length is too long */ - XcpSetCtoError(XCP_ERR_OUT_OF_RANGE); - return; + /* init resource check variable as if an illegal resource is requested */ + resourceOK = 0; + + /* check if calibration/paging resource is requested for seed/key and make + * sure this is the only requested resource + */ + if (((data[2] & XCP_RES_CALPAG) > 0) && ((data[2] & ~XCP_RES_CALPAG) == 0)) + { + resourceOK = 1; + } + + /* check if programming resource is requested for seed/key and make + * sure this is the only requested resource + */ + if (((data[2] & XCP_RES_PGM) > 0) && ((data[2] & ~XCP_RES_PGM) == 0)) + { + resourceOK = 1; + } + + /* check if data acquisition resource is requested for seed/key and make + * sure this is the only requested resource + */ + if (((data[2] & XCP_RES_DAQ) > 0) && ((data[2] & ~XCP_RES_DAQ) == 0)) + { + resourceOK = 1; + } + + /* check if data stimulation resource is requested for seed/key and make + * sure this is the only requested resource + */ + if (((data[2] & XCP_RES_STIM) > 0) && ((data[2] & ~XCP_RES_STIM) == 0)) + { + resourceOK = 1; + } + + /* now process the resource validation */ + if (resourceOK == 0) + { + XcpSetCtoError(XCP_ERR_OUT_OF_RANGE); + return; + } + + /* check if the resource is already unlocked */ + if ((xcpInfo.protection & data[2]) == 0) + { + /* set the seed length to 0 to indicate that the resource is already unlocked */ + xcpInfo.ctoData[1] = 0; + /* set packet length */ + xcpInfo.ctoLen = 2; + /* no need to continue processing */ + return; + } + + /* store resource for which the seed/key sequence is started */ + xcpInfo.s_n_k_resource = data[2]; } + /* process the mode flag. 0 is first part of the seed, 1 is remainder of the seed */ + if (data[1] == 0) + { + /* set flag that a seed reading sequence is now in progress */ + sequenceInProgress = BLT_TRUE; + /* obtain the seed and store it in the buffer */ + seedRemainderLen = XcpGetSeed(xcpInfo.s_n_k_resource, seedBuffer); + /* protect against buffer overrun */ + ASSERT_RT(seedRemainderLen <= XCP_SEED_MAX_LEN); + /* set seed pointer */ + seedCurrentPtr = &seedBuffer[0]; + } + /* seed remainder is requested */ + else + { + /* this is only allowed if a sequence is in progress */ + if (sequenceInProgress == BLT_FALSE) + { + /* invalid sequence */ + XcpSetCtoError(XCP_ERR_SEQUENCE); + /* reset seed/key resource variable for possible next unlock */ + xcpInfo.s_n_k_resource = 0; + return; + } + } + /* determine number of seed bytes that fit in the first response */ + seedCurrentLen = seedRemainderLen; + if (seedCurrentLen > (XCP_CTO_PACKET_LEN-2)) + { + seedCurrentLen = XCP_CTO_PACKET_LEN-2; + } + /* store the first part of the seed in the response */ + CpuMemCopy((blt_addr)(&xcpInfo.ctoData[2]), (blt_addr)seedCurrentPtr, seedCurrentLen); + xcpInfo.ctoData[1] = seedRemainderLen; + /* update control variables */ + seedRemainderLen -= seedCurrentLen; + seedCurrentPtr += seedCurrentLen; + /* reset sequence flag at the end of the sequence */ + if (seedRemainderLen == 0) + { + sequenceInProgress = BLT_FALSE; + } /* set packet length */ - xcpInfo.ctoLen = xcpInfo.ctoData[1] + 2; + xcpInfo.ctoLen = seedCurrentLen + 2; } /*** end of XcpCmdGetSeed ***/ @@ -989,41 +1041,82 @@ static void XcpCmdGetSeed(blt_int8u *data) ****************************************************************************************/ static void XcpCmdUnlock(blt_int8u *data) { - /* key cannot be longer than XCP_CTO_PACKET_LEN-2 */ - if (data[1] > (XCP_CTO_PACKET_LEN-2)) + /* made key buffer static to lower stack load */ + static blt_int8u keyBuffer[XCP_KEY_MAX_LEN]; + static blt_int8u keyPreviousRemainder = 0; + static blt_int8u keyTotalLen = 0; + static blt_int8u *keyCurrentPtr; + static blt_int8u keyReceivedLen = 0; + blt_int8u keyCurrentLen; + + /* verify that the key will actually fit in the buffer */ + if (data[1] > XCP_KEY_MAX_LEN) { - /* key is too long incorrect */ - XcpSetCtoError(XCP_ERR_SEQUENCE); + /* reset previous remainder for the next loop iteration */ + keyPreviousRemainder = 0; + /* key is too long */ + XcpSetCtoError(XCP_ERR_OUT_OF_RANGE); + /* reset seed/key resource variable for possible next unlock */ + xcpInfo.s_n_k_resource = 0; return; } - /* verify the key */ - if (XcpVerifyKey(xcpInfo.s_n_k_resource, &data[2], data[1]) == 0) + /* is this the start of a key reception? the first unlock message contains the total + * length of the key and subsequent messages the remainder length. if the received + * length is >= than the previously received remainder, it must be the reception + * start of a new key. + */ + if (data[1] >= keyPreviousRemainder) { - /* invalid key so inform the master and do a disconnect */ - XcpSetCtoError(XCP_ERR_ACCESS_LOCKED); + /* store the total length of the key */ + keyTotalLen = data[1]; + /* initialize pointer to key reception buffer */ + keyCurrentPtr = &keyBuffer[0]; + /* reset number of received key bytes */ + keyReceivedLen = 0; - /* indicate that the xcp connection is disconnected */ - xcpInfo.connected = 0; - - /* enable resource protection */ - XcpProtectResources(); - - return; } - - /* key correct so unlock the resource */ - xcpInfo.protection &= ~xcpInfo.s_n_k_resource; - - /* reset seed/key resource variable for possible next unlock */ - xcpInfo.s_n_k_resource = 0; + /* store length / remainder for checking during the next iteration */ + keyPreviousRemainder = data[1]; + /* determine how many key bytes were received */ + keyCurrentLen = data[1]; + if (keyCurrentLen > (XCP_CTO_PACKET_LEN-2)) + { + keyCurrentLen = XCP_CTO_PACKET_LEN-2; + } + /* store the received key bytes to the buffer */ + CpuMemCopy((blt_addr)keyCurrentPtr, (blt_addr)(&data[2]), keyCurrentLen); + /* update control variables */ + keyCurrentPtr += keyCurrentLen; + keyReceivedLen += keyCurrentLen; + /* check if the entire key was received */ + if (keyReceivedLen >= keyTotalLen) + { + /* reset previous remainder for the next loop iteration */ + keyPreviousRemainder = 0; + /* verify the key */ + if (XcpVerifyKey(xcpInfo.s_n_k_resource, keyBuffer, keyTotalLen) == 0) + { + /* invalid key so inform the master and do a disconnect */ + XcpSetCtoError(XCP_ERR_ACCESS_LOCKED); + /* indicate that the xcp connection is disconnected */ + xcpInfo.connected = 0; + /* reset seed/key resource variable for possible next unlock */ + xcpInfo.s_n_k_resource = 0; + /* enable resource protection */ + XcpProtectResources(); + return; + } + /* key correct so unlock the resource */ + xcpInfo.protection &= ~xcpInfo.s_n_k_resource; + /* reset seed/key resource variable for possible next unlock */ + xcpInfo.s_n_k_resource = 0; + } /* set packet id to command response packet */ xcpInfo.ctoData[0] = XCP_PID_RES; - /* report the current resource protection */ xcpInfo.ctoData[1] = xcpInfo.protection; - /* set packet length */ xcpInfo.ctoLen = 2; } /*** end of XcpCmdUnlock ***/ diff --git a/Target/Source/xcp.h b/Target/Source/xcp.h index f4ea99cd..39065f60 100644 --- a/Target/Source/xcp.h +++ b/Target/Source/xcp.h @@ -53,10 +53,10 @@ #if (BOOT_CPU_BYTE_ORDER_MOTOROLA > 0) /** \brief XCP byte ordering according to the Motorola (big-endian). */ -#define XCP_MOTOROLA_FORMAT (0x01) +#define XCP_MOTOROLA_FORMAT (0x01) #else /** \brief XCP byte ordering according to the Intel (little-endian). */ -#define XCP_MOTOROLA_FORMAT (0x00) +#define XCP_MOTOROLA_FORMAT (0x00) #endif /** \brief Enable (=1) or disable (=0) support for the calibration resource. This is @@ -240,6 +240,11 @@ /** \brief Use user defined algorithm. */ #define XCP_CS_USER (0xff) +/** \brief Maximum number of bytes of a seed for the seed/key security feature. */ +#define XCP_SEED_MAX_LEN (BOOT_XCP_SEED_MAX_LEN) +/** \brief Maximum number of bytes of a key for the seed/key security feature. */ +#define XCP_KEY_MAX_LEN (BOOT_XCP_KEY_MAX_LEN) + /**************************************************************************************** * Function prototypes