关于CSEc安全引导和顺序引导模式的问题

电子说

1.3w人已加入

描述

1. 4kB EERAM上的CSEc密钥和用户数据

    使用S32K148,将启用CSE模块进行安全引导。根据应用说明(“使用S32K148 FlexNVM存储器”,AN12003,版本0,2017年7月),在第3.3节中,64 kB的FlexNVM用作闪存(EEPROM备份和密钥存储在一起),CSEc使用最多512字节的密钥存储,剩下的3.5 kB EERAM用于EEPROM和启用CSEc。在这种用法中,用户数据是否以某种方式通过覆盖或删除注册密钥而保留。简而言之,记录密钥的512字节是随机记录在EERAM中,还是记录在特定位置,如4KB FlexRAM的开始/结束位置?

-> 如果分配的CSEc空间为512B,则FlexRAM(EEPROM)中将无法访问空间0x14000E00-0x14000FFF。对该区域的任何访问都会生成总线故障异常。

2. 关于CSEc安全引导和顺序引导模式的问题。

    测量从复位引脚释放到高电平的安全启动时间,不同boot_SIZE和Clock时钟对于安全启动时间有所不同。当安全启动开始启动时(即CSEc正在计算CMAC),MCU内核保持在复位状态,复位引脚也保持在低电平状态,直到CMAC计算完成。

寄存器

3. 实现 SHA256

    在S32K1上面的CSEc 模块支持 AES128 引擎,但不支持 SHA256。但是用户可以参考如下代码实现它。

https://github.com/ARMmbed/mbedtls/blob/v2.16.8/library/sha256.c

4. 当在 S32K146中使用 CSEc模块的时候,程序访问 CSE_PRAM 空间,在调试模式下它将进入异常中断。

#define REG_WRITE32(address, value) ((*(volatile uint32*)(address))= (value))

#define CRYPTO_PRAM_HDR_ADDR32 (0x14001000u)

REG_WRITE32(CRYPTO_PRAM_HDR_ADDR32, u32CommandHeader);

寄存器

    上述代码高亮部分表示写入 PRAM命令,会导致程序进入异常中断。

-> 通过检查SIM_SDID[FEATURES]寄存器,判断S32K芯片是否支持 CSEc。为了使能 CSEc 模块,内存需要分区用于EEPROM,FTFC寄存器需要编程。

寄存器

5. CSEC模块PRAM格式

    在应用笔记 AN5401中,有的是通过 PRAM 页1读取数据,而有的是通过 PRAM页2输出数据,这两种方法有什么不同的吗?

寄存器

寄存器

-> 参考手册里面的 CSEc命令

https://www.nxp.com/docs/en/reference-manual/S32K-RM.pdf 

    这是CMD_GET_ID命令的描述,可以在其中看到使用了哪些页及其目的是什么:

寄存器

    如下是 AN5401文档中的参考代码部分。

寄存器

6. CSEc不能恢复到工厂模式

    通过 CSEC_DRV_DbgChal恢复到工厂模式失败,返回 "STATUS_SEC_KEY_WRITE_PROTECTED"。这里Keys不能被配置为写保护,否则将不能被擦除。

https://www.nxp.com/docs/en/application-note/AN12130.pdf

寄存器

7. S32K144 闪存分区问题

    使用S32K144EVB-Q100和SDK3.0.0以及S32DS进行ARM调试。当使用OpenSDA工具调试闪存时,可以擦除闪存并完成分区闪存,但当使用J-Link调试闪存时无法再次擦除闪存和分区闪存,必须先使用类似J-Flash的工具擦除闪存。如何使用J-Link擦除闪存和分区闪存。是否有调试配置的任何设置?

->在使用SDK API对FlexNVM进行分区时,由于启用了CSEc。

寄存器

    如果启用CSEc,则无法通过命令“全部擦除”或其他FTFC命令擦除FlexNVM。如果要擦除FlexNVM,唯一的方法是使用CSEc命令将FlexNVM复位为出厂状态。有关更多信息,请参阅AN5401。

https://www.nxp.com/webapp/Download?colCode=AN5401

https://www.nxp.com/webapp/Download?colCode=AN5401SW&docLang=en

#include "CSEc_functions.h"

#include "CSEc_macros.h"

#include "CSEc_keys.h"

uint32_t dbg_challenge_out[4] = {0,0,0,0};

int main(void)

{

uint16_t __attribute__((unused)) csec_error = 0; //1 means No Error

csec_error = INIT_RNG(); /* Initialize the Random Number Generator before generating challenge */

    //To Erase all keys and reset the part to the factory state

    csec_error = DBG_CHAL(dbg_challenge_out); /* Generate the Challenge */

    csec_error = DBG_AUTH(dbg_challenge_out); /* Issue the Authorization */

while(1);

    /* to avoid the warning message for GHS and IAR: statement is unreachable*/

#if defined (__ghs__)

#pragma ghs nowarning 111

#endif

#if defined (__ICCARM__)

#pragma diag_suppress=Pe111

#endif

return 0;

}

    由于 CSEc 模块所有密钥都只能 CSEc 模块管理,这样才能确保密钥安全的安全性。虽然密钥实际存储区域是在模拟 EEPROM 中,但由于这段区域是由 CSEc 管理并且对用户是不可见的,所以也无法通过直接对 DFlash 编程的方式写入密钥。在量产时,可以使用一份专用于写入密钥的程序,通过先刷写这份程序,等写入密钥后,再刷写正式的应用程序即可。需要注意的时,两份程序需要有一致的 D-Flash 分区策略。当然也可以通过诊断服务的方式加载密钥。先脱机计算出密钥的 M1~M5 值,然后通过诊断服务(如 2Eh服务)将 M1~M3 加载至 CSEc 模块中,并通过 M4 和 M5 验证是否加载成功。由于不能通过 M1~M3 值逆推算出密钥值,所以这种方式也是安全的。由于使能 CSEc 模块后,只能通过 CSEc 模块的恢复命令(使用调试器也不行)才能擦除 D-Flash 数据并恢复 D-Flash 至出厂状态,所以如果在量产后需要再次擦除 D-Flash,或者重新对 D-Flash 进行分区等,可以在用户代码中加入相关程序,通过触发这段程序来使用 CSEc 模块的命令擦除 D-Flash。CSEc 模块对 P-Flash的重新编程没有影响。

7. 为CSEc操作分配密钥大小后,需要按照AN5401 4.5中列出的步骤将闪存重置为出厂状态。此外,还提供了一些连接/擦除/编程到S32K器件的提示。

(https://www.nxp.com/docs/en/application-note/AN12130.pdf )

如果没有按照正确的步骤在分配密钥后擦除Flash,器件可能会被锁定,并且无法解锁。

如参考手册所述,分区操作在整个产品周期中最好只执行一次。

寄存器

寄存器

可以通过DBG_CHAL 和 DBG_AUTH 销毁分区,不用擦除Flash。

寄存器

    当未分配CSEc时,没有办法在不擦除所有键的情况下删除分区? 因为只需要擦除数据Flash 以及Flash IFR。但是,一旦数据Flash 已经为EEPROM分区,FTFC擦除Flash 块命令就不能擦除数据Flash。

寄存器

    运行DBG_CHAL和DBG_ AUTH命令会擦除所有密钥,包括BOOT_MAC和BOOT_ MAC_KEY。在没有这两个参数的情况下,不会执行安全引导过程,因此应用程序可以自由执行,并且MCU 不会处于复位状态。

    DBG_CHAL/DBG_ AUTH即使在严格启动模式处于活动状态时也可以工作,假设所有密钥都没有写保护。这不会锁定 MCU。

    成功执行DBG_CHAL/DBG_ AUTH后,所有密钥将被擦除,安全引导处于非活动状态。可以使用任何方式(串行、并行、严格顺序)再次激活安全引导。

注意:对于严格顺序模式,自动MAC计算是不可能的。在激活严格顺序引导之前,必须计算并存储BOOT_MAC。否则将锁定器件。MAC可通过CSEc本身或PC离线计算。有关更多详细信息和代码示例,请参阅 AN5401。

8.  当执行FLASH_DRV_DEFlashPartition 函数的时候, FTFx_FSTAT 是FTFx_FSTAT_ACCERR_MASK。

While((FTFC->FSTAT&FTFC_FSTAT_CCIF_MASK)==0U)

FTFC_FSTAT – 异常时,Flash状态寄存器是 160, Flash Access Error
Flag 标志是 '1',但是正常状态是 128。Flash已经分区,有如下原因导致 ACCERR位被置位。

寄存器

    已通过分区命令启用CSEc,则此时擦除所有块命令将无效(它也应返回ACCERR)。是否加载了密钥并不重要。一旦Flash 分区,并且密钥大小不是 0,则需要使用CMD_DBG_CHAL和CMD_ DBG_ AUTH命令来销毁分区。为此,需要知道MASTER_ECU_KEY。如果尚未加载MASTER_ECU_KEY,则需要加载,然后可以使用CMD_DBG_CHAL和CMD_ DBG_ AUTH命令,别无选择。可以检查SIM模块中的闪存配置寄存器1(FCFG1),查看器件是否已分区。

    如果Flash已经分区、CSEc已经使能,FLASH_DRV_EraseAllBlock不能工作。唯一的方案是在了解MASTER_ECU_KEY的情况下使用DBG_CHAL 和 DBG_AUTH 命令恢复。

  可以参考如下例子中的 eraseKeys()函数。S32DS.3.4S32DSsoftwareS32SDK_S32K1XX_RTM_4.0.2examplesS32K144driver_examplessystemcsec_keyconfig

寄存器

    在HSRUN模式(112MHz下),CSEc (安全) 或者 EEPROM 擦写将触发错误标志。因为在这种情况下不允许同时使用。器件需要切回到 RUN模式 (80 Mhz) 来执行 CSEc(安全) 或者 EEPROM 写/擦除。

寄存器

    Flash 内存安全在 CSEc 和non-CSEc 器件上都支持,SIM_SDID[7]表示 CSEc 是否在器件上支持。CSEc 和non CSEc 用户需要运行 PGMPART 命令来配置 KeySize。针对 non CSEc 用户,Key Size 必须配置为 0。

    整个 EEERAM的空间是减小,为了存储用户 Keys的需要。用户密钥空间实际上成为EEERAM中的不可寻址空间。 针对具有 CSEc或者没有 CSEc的器件,如果 Key size为0,则CSEc_PRAM访问是不允许的。Keysize为零时,是普通EEPROM分区么,直接mass erase就恢复了。通过操作 FLASH_DRV_EraseAllBlockUnsecure函数恢复,或者硬件将Reset引脚短接,然后通过JLINK等外部工具擦除MCU内部的 Flash。

寄存器

寄存器

    如果S32K146板默认存在分区,通过判断(SIM->FCFG1[FEATURE])确定分区,如果要使用新的分区操作,需要擦除旧的分区,如果不擦,在调试CSEc init_rng或者erase all key的时候会进入defaultISR中断,可以通过上述mass erase方式恢复(keysize为零的情况)。然后重新分区使能 CSEc模块,操作初始化随机数以及加解密,预置秘钥操作。

    程序分区命令准备FlexNVM块用作数据闪存、模拟EEPROM备份或两者的组合,并初始化FlexRAM。程序分区命令不能从Flash 启动,因为在程序分区命令执行期间无法访问Flash 资源。与程序分区命令的执行相关的更改在下次复位后生效。在启动其他FTFC和CSEc命令之前,预计将为新器件运行程序分区命令。

注意:虽然FlexNVM可以用不同分区,但其目的是在给定应用程序的整个生命周期中使用一次分区选择。FlexNVM分区代码选择影响器件的耐久性和数据保留特性。中断程序分区操作(由于断电、复位、电源超出指定的操作范围或任何其他原因)会使分区代码处于不确定状态。用户必须采取适当的应对措施,以防止程序分区操作中断时数据丢失。

    对于未启用CSEc的部件,Flash KeySize的数量必须配置为2'b00。对于启用CSEc的器件,Flash密钥的数量是用户可配置的,但该空间将假设存在MASTER_ECU_KEY、BOOT_MAC_KEY和BOOT_ MAC(如果启用了任何密钥),因此将占用可用20个密钥槽空间中的3个密钥槽。这导致在1到17个用户keys的范围内留下键槽。对于未启用CSEc的部件,密钥分配必须设置为零密钥(2'b00),否则该命令将返回错误。

    注:对于具有CSEc的器件,一旦分配了Flash密钥(无论是否初始化),SHE规范中不带认证将不能擦除Flash 密钥的要求将适用。这意味着在擦除数据Flash(所有Flash 密钥都备份在数据Flash中的模拟EEPROM中)之前,必须运行并通过身份验证(DBG_CHAL和DBG_ AUTH)命令(删除所有 Flash密钥)。因此,擦除所有块和擦除所有块解锁将不起作用,如果所选块/扇区包括存储密钥,则擦除Flash 块或扇区也不起作用。此外,如果任何 Flash密钥受写保护,则无法擦除/删除它们,因此无法擦除数据 Flash,身份验证过程也不会通过。

9. 在S32DS环境下,使用如下 RTM3.0.0的example flash_partitioning_s32k144的例子,在debug RAM下运行就可以擦除以前老的 EEPROM分区。(如果keysize为零,没有使用 CSEc的情况下)

/* Including needed modules to compile this module/procedure */

#include "Cpu.h"

#include "clockMan1.h"

#include "Flash.h"

volatile int exit_code = 0;

/* User includes (#include below this line is not maintained by Processor Expert) */

#include

#include

/* Declare a FLASH config struct which initialized by FlashInit, and will be used by all flash operations */

flash_ssd_config_t flashSSDConfig;

/* Data source for program operation */

#define BUFFER_SIZE         0x100u          /* Size of data source */

#define FLASH_TARGET1

uint8_t sourceBuffer[BUFFER_SIZE];

/* Function declarations */

void CCIF_Handler(void);

/* If target is flash, insert this macro to locate callback function into RAM */

void CCIF_Callback(void)

int main(void)

{

/* Write your local variable definition here */

status_t ret;        /* Store the driver APIs return code */

uint32_t address;

uint32_t size;

uint32_t failAddr;

uint32_t i;

flash_callback_t pCallBack;

#if (FEATURE_FLS_HAS_PROGRAM_PHRASE_CMD == 1u)

#ifndef FLASH_TARGET

uint8_t unsecure_key[FTFx_PHRASE_SIZE] = {0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFEu, 0xFFu, 0xFFu, 0xFFu};

#endif

#else   /* FEATURE_FLASH_HAS_PROGRAM_LONGWORD_CMD */

uint8_t unsecure_key[FTFx_LONGWORD_SIZE] = {0xFEu, 0xFFu, 0xFFu, 0xFFu};

#endif  /* FEATURE_FLS_HAS_PROGRAM_PHRASE_CMD */

/*** Processor Expert internal initialization. DON'T REMOVE THIS CODE!!! ***/

#ifdef PEX_RTOS_INIT

PEX_RTOS_INIT();                   /* Initialization of the selected RTOS. Macro is defined by the RTOS component. */

#endif

/*** End of Processor Expert internal initialization.                    ***/

CLOCK_SYS_Init(g_clockManConfigsArr, CLOCK_MANAGER_CONFIG_CNT,

g_clockManCallbacksArr, CLOCK_MANAGER_CALLBACK_CNT);

CLOCK_SYS_UpdateConfiguration(0U, CLOCK_MANAGER_POLICY_AGREEMENT);

/* Init source data */

for (i = 0u; i < BUFFER_SIZE; i++)

{

sourceBuffer[i] = i;

}

/* Disable cache to ensure that all flash operations will take effect instantly,

* this is device dependent */

#ifndef FLASH_TARGET

#ifdef S32K144_SERIES

MSCM->OCMDR[0u] |= MSCM_OCMDR_OCM1(0x3u);

MSCM->OCMDR[1u] |= MSCM_OCMDR_OCM1(0x3u);

#endif /* S32K144_SERIES */

#endif /* FLASH_TARGET */

/* Install interrupt for Flash Command Complete event */

INT_SYS_InstallHandler(FTFC_IRQn, CCIF_Handler, (isr_t*) 0);

INT_SYS_EnableIRQ(FTFC_IRQn);

/* Enable global interrupt */

INT_SYS_EnableIRQGlobal();

/* Always initialize the driver before calling other functions */

ret = FLASH_DRV_Init(&Flash_InitConfig0, &flashSSDConfig);

DEV_ASSERT(STATUS_SUCCESS == ret);

#if ((FEATURE_FLS_HAS_FLEX_NVM == 1u) & (FEATURE_FLS_HAS_FLEX_RAM == 1u))

/* Config FlexRAM as EEPROM if it is currently used as traditional RAM */

if (flashSSDConfig.EEESize == 0u)

{

#ifndef FLASH_TARGET

/* First, erase all Flash blocks if code is placed in RAM to ensure

* the IFR region is blank before partitioning FLexNVM and FlexRAM */

ret = FLASH_DRV_EraseAllBlock(&flashSSDConfig);

DEV_ASSERT(STATUS_SUCCESS == ret);

/* Verify the erase operation at margin level value of 1 */

ret = FLASH_DRV_VerifyAllBlock(&flashSSDConfig, 1u);

DEV_ASSERT(STATUS_SUCCESS == ret);

/* Reprogram secure byte in Flash configuration field */

#if (FEATURE_FLS_HAS_PROGRAM_PHRASE_CMD == 1u)

address = 0x408u;

size = FTFx_PHRASE_SIZE;

#else   /* FEATURE_FLASH_HAS_PROGRAM_LONGWORD_CMD == 1u */

address = 0x40Cu;

size = FTFx_LONGWORD_SIZE;

#endif /* FEATURE_FLS_HAS_PROGRAM_PHRASE_CMD */

ret = FLASH_DRV_Program(&flashSSDConfig, address, size, unsecure_key);

DEV_ASSERT(STATUS_SUCCESS == ret);

#endif /* FLASH_TARGET */

/* Configure FlexRAM as EEPROM and FlexNVM as EEPROM backup region,

* DEFlashPartition will be failed if the IFR region isn't blank.

* Refer to the device document for valid EEPROM Data Size Code

* and FlexNVM Partition Code. For example on S32K144:

* - EEEDataSizeCode = 0x02u: EEPROM size = 4 Kbytes

* - DEPartitionCode = 0x08u: EEPROM backup size = 64 Kbytes */

ret = FLASH_DRV_DEFlashPartition(&flashSSDConfig, 0x02u, 0x08u, 0x0u, false, true);

DEV_ASSERT(STATUS_SUCCESS == ret);

/* Re-initialize the driver to update the new EEPROM configuration */

ret = FLASH_DRV_Init(&Flash_InitConfig0, &flashSSDConfig);

DEV_ASSERT(STATUS_SUCCESS == ret);

/* Make FlexRAM available for EEPROM */

ret = FLASH_DRV_SetFlexRamFunction(&flashSSDConfig, EEE_ENABLE, 0x00u, NULL);

DEV_ASSERT(STATUS_SUCCESS == ret);

}

else    /* FLexRAM is already configured as EEPROM */

{

ret = FLASH_DRV_EraseAllBlockUnsecure(&flashSSDConfig);

DEV_ASSERT(STATUS_SUCCESS == ret);

/* Re-initialize the driver to update the new EEPROM configuration */

ret = FLASH_DRV_Init(&Flash_InitConfig0, &flashSSDConfig);

DEV_ASSERT(STATUS_SUCCESS == ret);

}

#endif /* (FEATURE_FLS_HAS_FLEX_NVM == 1u) & (FEATURE_FLS_HAS_FLEX_RAM == 1u) */

/* Set callback function before a long time consuming flash operation

* (ex: erasing) to let the application code do other tasks while flash

* in operation. In this case we use it to enable interrupt for

* Flash Command Complete event */

pCallBack = (flash_callback_t)CCIF_Callback;

flashSSDConfig.CallBack = pCallBack;

/* Erase the last PFlash sector */

address = FEATURE_FLS_PF_BLOCK_SIZE - FEATURE_FLS_PF_BLOCK_SECTOR_SIZE;

size = FEATURE_FLS_PF_BLOCK_SECTOR_SIZE;

ret = FLASH_DRV_EraseSector(&flashSSDConfig, address, size);

DEV_ASSERT(STATUS_SUCCESS == ret);

/* Disable Callback */

flashSSDConfig.CallBack = NULL_CALLBACK;

/* Verify the erase operation at margin level value of 1, user read */

ret = FLASH_DRV_VerifySection(&flashSSDConfig, address, size / FTFx_DPHRASE_SIZE, 1u);

DEV_ASSERT(STATUS_SUCCESS == ret);

/* Write some data to the erased PFlash sector */

size = BUFFER_SIZE;

ret = FLASH_DRV_Program(&flashSSDConfig, address, size, sourceBuffer);

DEV_ASSERT(STATUS_SUCCESS == ret);

/* Verify the program operation at margin level value of 1, user margin */

ret = FLASH_DRV_ProgramCheck(&flashSSDConfig, address, size, sourceBuffer, &failAddr, 1u);

DEV_ASSERT(STATUS_SUCCESS == ret);

/* Try to write data to EEPROM if FlexRAM is configured as EEPROM */

if (flashSSDConfig.EEESize != 0u)

{

address = flashSSDConfig.EERAMBase;

size = sizeof(uint32_t);

ret = FLASH_DRV_EEEWrite(&flashSSDConfig, address, size, sourceBuffer);

DEV_ASSERT(STATUS_SUCCESS == ret);

/* Verify the written data */

if (*((uint32_t *)sourceBuffer) != *((uint32_t *)address))

{

/* Failed to write data to EEPROM */

exit_code = 1u;

return exit_code;

}

/* Try to update one byte in an EEPROM address which isn't aligned */

address = flashSSDConfig.EERAMBase + 1u;

size = sizeof(uint8_t);

sourceBuffer[0u] = 0xFFu;

ret = FLASH_DRV_EEEWrite(&flashSSDConfig, address, size, sourceBuffer);

DEV_ASSERT(STATUS_SUCCESS == ret);

/* Then verify */

if (sourceBuffer[0u] != *((uint8_t *)address))

{

/* Failed to update data to EEPROM */

exit_code = 1u;

return exit_code;

}

}

else

{

#if (FEATURE_FLS_HAS_FLEX_NVM == 1u)

/* Erase a sector in DFlash */

address = flashSSDConfig.DFlashBase;

size = FEATURE_FLS_DF_BLOCK_SECTOR_SIZE;

ret = FLASH_DRV_EraseSector(&flashSSDConfig, address, size);

DEV_ASSERT(STATUS_SUCCESS == ret);

/* Verify the erase operation at margin level value of 1, user read */

ret = FLASH_DRV_VerifySection(&flashSSDConfig, address, size / FTFx_PHRASE_SIZE, 1u);

DEV_ASSERT(STATUS_SUCCESS == ret);

/* Write some data to the erased DFlash sector */

address = flashSSDConfig.DFlashBase;

size = BUFFER_SIZE;

ret = FLASH_DRV_Program(&flashSSDConfig, address, size, sourceBuffer);

DEV_ASSERT(STATUS_SUCCESS == ret);

/* Verify the program operation at margin level value of 1, user margin */

ret = FLASH_DRV_ProgramCheck(&flashSSDConfig, address, size, sourceBuffer, &failAddr, 1u);

DEV_ASSERT(STATUS_SUCCESS == ret);

#endif /* FEATURE_FLS_HAS_FLEX_NVM */

}

  #ifdef PEX_RTOS_START

    PEX_RTOS_START();                  /* Startup of the selected RTOS. Macro is defined by the RTOS component. */

  #endif

  for(;;) {

    if(exit_code != 0) {

      break;

    }

  }

  return exit_code;

  /*** Processor Expert end of main routine. DON'T WRITE CODE BELOW!!! ***/

} /*** End of main routine. DO NOT MODIFY THIS TEXT!!! ***/

void CCIF_Handler(void)

{

/* Disable Flash Command Complete interrupt */

FTFx_FCNFG &= (~FTFx_FCNFG_CCIE_MASK);

return;

}

void CCIF_Callback(void)

{

/* Enable interrupt for Flash Command Complete */

if ((FTFx_FCNFG & FTFx_FCNFG_CCIE_MASK) == 0u)

{

FTFx_FCNFG |= FTFx_FCNFG_CCIE_MASK;

}

}

寄存器

    运行调试模式,停止,然后再次运行会进入FLASH_DRV_EraseAllBlockUnsecure,关于 Flash组件配置如下。

寄存器

  审核编辑:汤梓红
 
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分