iMXRT1021 flexspiNOR unable to use HAB Encrypted XIP

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

iMXRT1021 flexspiNOR unable to use HAB Encrypted XIP

8,183 Views
t_thurgood
Contributor III

Hi,

I am using the iMXRT1021 with flexqspiNOR flash. We want to encrypt the .sb image and the use on-the-fly BEE decryption. I am using the elftosb.exe, sdphost and blhost to produce and flash the .sb image.

This all works for normal (unsigned) image, but I cannot get the past the first stage of encryption.

I have downloaded, "Code-signing-tools", "openssl", "ubuntu shell for win10". I can create all the SRKs and certificates.

elftosb\elftosb.exe -f imx -V -c bd_file\imx-flexspinor-normal-signed.bd -o elftosb\img\ivt_output_xip.bin elftosb\img\hermes.s19

results in a ivt_output_xip.bin with 0kB.

Second stage..

elftosb\elftosb.exe -f kinetis -V -c bd_file\program_flexspinor_image_qspinor_encrypt.bd -o elftosb\img\encrypt_hermes_image.sb elftosb\img\ivt_output_xip_nopadding.bin

results in...

failed to open source file: elftosb\img\ivt_output_xip_nopadding.bin (ignoring for now)
error: line 55: error opening source 'myBinFile'

And no .sb image is produced.

Please advise how I can generate an encrypted .sb file, download and execute with BEE.

best regards,

Tony

imx-flexspinor-normal-signed.bd

options {
flags = 0x04;
startAddress = 0x60000000;
ivtOffset = 0x0400;
initialLoadSize = 0x2000;
//DCDFilePath = "dcd.bin";
# Note: This is required if the cst and elftsb are not in the same folder
// cstFolderPath = "/Users/nxf38031/Desktop/CSTFolder";
cstFolderPath = "/Projects/code_signing_tool/cst-3.2.0/release/";

# Note: This is required if the default entrypoint is not the Reset_Handler
# Please set the entryPointAddress to Reset_Handler address
// entryPointAddress = 0x60002411;
entryPointAddress = 0x60019358;
}

sources {
elfFile = extern(0);
}

constants {
SEC_CSF_HEADER = 20;
SEC_CSF_INSTALL_SRK = 21;
SEC_CSF_INSTALL_CSFK = 22;
SEC_CSF_INSTALL_NOCAK = 23;
SEC_CSF_AUTHENTICATE_CSF = 24;
SEC_CSF_INSTALL_KEY = 25;
SEC_CSF_AUTHENTICATE_DATA = 26;
SEC_CSF_INSTALL_SECRET_KEY = 27;
SEC_CSF_DECRYPT_DATA = 28;
SEC_NOP = 29;
SEC_SET_MID = 30;
SEC_SET_ENGINE = 31;
SEC_INIT = 32;
SEC_UNLOCK = 33;
SEC_XIP_REGION0 = 34;
SEC_XIP_REGION1 = 35;
}

section (SEC_CSF_HEADER;
Header_Version="4.2",
Header_HashAlgorithm="sha256",
Header_Engine="DCP",
Header_EngineConfiguration=0,
Header_CertificateFormat="x509",
Header_SignatureFormat="CMS"
)
{
}

section (SEC_CSF_INSTALL_SRK;
InstallSRK_Table="crts/SRK_1_2_3_4_table.bin", // "valid file path"
InstallSRK_SourceIndex=0
)
{
}

section (SEC_CSF_INSTALL_CSFK;
InstallCSFK_File="crts/CSF1_1_sha256_2048_65537_v3_usr_crt.pem", // "valid file path"
InstallCSFK_CertificateFormat="x509" // "x509"
)
{
}

section (SEC_CSF_AUTHENTICATE_CSF)
{
}

section (SEC_CSF_INSTALL_KEY;
InstallKey_File="crts/IMG1_1_sha256_2048_65537_v3_usr_crt.pem",
InstallKey_VerificationIndex=0, // Accepts integer or string
InstallKey_TargetIndex=2) // Accepts integer or string
{
}

section (SEC_CSF_AUTHENTICATE_DATA;
AuthenticateData_VerificationIndex=2,
AuthenticateData_Engine="DCP",
AuthenticateData_EngineConfiguration=0)
{
}

section (SEC_SET_ENGINE;
SetEngine_HashAlgorithm = "sha256", // "sha1", "Sha256", "sha512"
SetEngine_Engine = "DCP", // "ANY", "SAHARA", "RTIC", "DCP", "CAAM" and "SW"
SetEngine_EngineConfiguration = "0") // "valid engine configuration values"
{
}


section (SEC_UNLOCK;
Unlock_Engine = "SNVS", // "SRTC", "CAAM", SNVS and OCOTP
Unlock_features = "ZMK WRITE" // "Refer to Table-24"
)
{
}

Labels (1)
81 Replies

1,550 Views
jay_heng
NXP Employee
NXP Employee

fuse_setting.json only work with 'Save' and 'Load' buttons in eFuse Operation Utility Window, it is about fuse value editting in tool UI, You need to click 'Burn' button to burn eFuse of RT chip

0 Kudos

1,550 Views
t_thurgood
Contributor III

Hi Jay,

One of the problems was that the HAB fuse SEC_CONFIG[1:0]  was blown as part of the "BEE Encrypted Image Boot" download action. I was trying to inhibit that action, as it is not necessarily required during development and it would remove one reason for my target board not running.

So one of my requests to Kerry was; can we as end users have more control over the fuse blowing.

The EncryptedXIP and BT_FUSE_SEL fuses also need to be blown as I am using a target that has no external Boot pin capability. The latter prevents further access to fuse blowing, so it is very important that all required fuses are blown before exiting the tool.

0 Kudos

1,550 Views
kerryzhou
NXP TechSupport
NXP TechSupport

Hi Tony Thurgood,

   If you don't have the Encrypted related pins, you can modify the fuse related bit before you exiting the tool, or before you do the BEE encrypted image boot download action, after you connect your chip, you can do the fuse scan and burn.

pastedImage_1.png

   BT_FUSE_SEL is in 0x460:

pastedImage_2.png

   After you burn the fuse bit(EncryptedXIP and BT_FUSE_SEL), you can do the BEE download again, then if you exit the tool, you don't need to use the EncryptedXIP external pins, it will boot from fuse.

Wish it helps you!

Kerry

0 Kudos

1,550 Views
t_thurgood
Contributor III

kerryzhou

Yes I know about those fuses and I have done all those things, but my board does not run. When the BT_FUSE_SEL is blown and the tool exited, I don't believe it is possible for further Tool/Flashloader communication. If the code doesn't run, the board is bricked.

Your .png picture shows iMXRT106x. That concerns me, because I would like you to focus on iMXRT1021. The EncryptedXIP fuse is at a different address for 1021.

1. Have you or Jay been able to demonstrate the "BEE Encrypted Image Boot" running on a 1021 target?

2. When I download the code for encryption, I view the flash memory and it is un-encrypted, why?

3. The Tool, in BEE mode downloads the image code before blowing BEE_KEY0_SEL, is that correct?

4. Image is written from location 5242000 (0x60001000) ...is that correct for 1021?

5. Should I download the image.bin or image.sb?

When I read NXP-MCUBootUtility/README.md at master · JayHeng/NXP-MCUBootUtility · GitHub, section 3.3 says .bin, section 3.4 says .sb. The .sb can only be generated by "All in one action", but I am unsure what else that action will do or not do, to my target board. Master mode should allow more control/choice over the actions.

Please try following my points and let me know asap how you get on.

br,

Tony

0 Kudos

1,550 Views
kerryzhou
NXP TechSupport
NXP TechSupport

Hi Tony Thurgood,

  So sorry for the delay reply for you.

  I have finished the BEE testing with  fixed otpmk( SNVS )key, I have checked the image region, it is really secured.

  I will give you reply in the last range of this post, just let this post seems a little complete.

  Please go to the last reply in this post.

Best Regards,

Kerry

0 Kudos

1,550 Views
rshipman
Contributor V

Hi Tony,

I'm sure you've done this already, but just in case, extracting the HAB errors can be useful:

https://community.nxp.com/thread/521756 

Regards,

Ronnie

0 Kudos

1,550 Views
rshipman
Contributor V

Hi Tony,

Yes you do need to blow that fuse for BEE decryption to work. (Not so sure about DCP encryption. You probably don't.)

However you don't need to blow it for development. You can set pin GPIO_EMC_18 high, at least during boot, until you need that pin for something else or you are finished with BEE. That's what the SW8-1 dip switch does on the eval board - but you'll have to devise your own thing. The ROM bootloader apparently routes that pin to BOOT_CFG[0] during boot.

Hope that helps.

Regards,

Ronnie

0 Kudos

1,550 Views
t_thurgood
Contributor III

?

0 Kudos

1,550 Views
john8
Contributor III

Hi,

Did you resolve this? Having similar issues.

regards

0 Kudos

1,550 Views
t_thurgood
Contributor III

Hi John,

I'm waiting for advice on this.

I'm reluctant to try various options because it would mean blowing the OTP fuses and bricking more boards than I have available.

br,

Tony

0 Kudos

1,550 Views
john8
Contributor III

Hi Tony,

I got it going using scripts and the image_enc code from Jay Hengs MCUBootUitility.

Also, MCUBootUtility has bugs... the all in one action fails in some circumstances, specifically BEE Encryption. It does not create the sb files as it is supposed to. Basically it is unusable and can only be used as a HAB/BEE research source.

Basically, using scripts and a modified image_enc tool, I can do all HAB signing and BEE Image encryption myself as part of my build. I then use the flashloader or JTAG to program the Flash.

I would post it here but I'm not sure if I'm allowed to.

0 Kudos

1,550 Views
jay_heng
NXP Employee
NXP Employee

Can you please create an issue here? Issues · JayHeng/NXP-MCUBootUtility · GitHub 

0 Kudos

1,550 Views
t_thurgood
Contributor III

Hi John,

That's good to hear, well done.

If you can provide me with your methods, I would very much appreciate it.

Another challenge for me is firmware upgrade. I will need to setup the BEE for on-the-fly encryption when copying upgraded f/w to another bank in flash.

br,

Tony

0 Kudos

1,550 Views
john8
Contributor III

Hi Tony,

I am posting here for everyones benefit:

 

I will just explain what I do and give you some script samples and code.

You should be able to work the rest out, but ask me if you need to.

 

NB: I have now switched to a EVK-RT1060 board, but everything should apply to a RT1021 or RT1051

NB: SW_GP4 is not available on RT1020/RT1050 – this is the second AES key for BEE Region 1, else Key is shared.

NB: It is likely that Jay Hengs MCUBootUtility has messed up your EVK board. I ran that utility using the BEE Encrypted Mode on my EVK-RT1064 and it got bricked!!!

NB: I only use the utility to scan/read fuses and to do mass erase/view HAB enabled code.

NB: I have used the source code to gain a better understanding than the documents alone.

NB: Do you have the 1050SRM security reference manual document? You will need it.

NB: Do you have AN12079 BEE application note, useful.

 

  1. Create keys.

Use the hab4_pki_tree script to create the keys (.bat or .sh), optionally modify it to be non-interactive

Use the following script to create the SRK hashes:

#run SRK keys creation batch file

cd C:\work\HAB\Flashloader_RT1020_1.0_GA\Tools\elftosb\win\keys 

 

.\hab4_pki_tree_NoQuestions

 

#create SRK hash

cd C:\work\HAB\Flashloader_RT1020_1.0_GA\Tools\elftosb\win\mingw32\bin

 

./srktool -h 4 -t ../../keys/SRK_1_2_3_4_table.bin -e ../../keys/SRK_1_2_3_4_fuse.bin -d sha256 `

-c ../../crts/SRK1_sha256_2048_65537_v3_ca_crt.pem, `

../../crts/SRK2_sha256_2048_65537_v3_ca_crt.pem, `

../../crts/SRK3_sha256_2048_65537_v3_ca_crt.pem, `

../../crts/SRK2_sha256_2048_65537_v3_ca_crt.pem -f 1

 

  1. Burn the SRK HAB Hash fuses if using HAB

NB: I don’t close the HAB, leave it open. Only close for production with production keys.

Sample .BD file below

 

# The source block assign file name to identifiers

sources {

}

 

constants {

}

 

# The section block specifies the sequence of boot commands to be written to the SB file

# Note: These are DEVELOPMENT SRK keys

#

# WARNING: DO NOT USE THESE KEYS FOR PRODUCTION BOARDS

 

section (0) {

# Program SRK table

load fuse 0x577d575c > 0x18;

load fuse 0x5fce2697 > 0x19;

load fuse 0x556bf2ae > 0x1a;

load fuse 0x53c4f3fa > 0x1b;

load fuse 0x51aad22b > 0x1c;

load fuse 0x525acce5 > 0x1d;

load fuse 0x50c8fab3 > 0x1e;

load fuse 0x5b8f4102 > 0x1f;

 

#WARNING: DO NOT LOCK DEV BOARD HAB FOR DEVELOPMENT, makes dev life harder!

#HAB still works for as it should but allows booting if authentication fails

#memory location 0x20201000, size 0xB80 for has HAB events.

#use linux tool “hab_log_parser” to parse HAB events

#do not close HAB if events are reported.

#

# Should be locked for production of course

# Program SEC_CONFIG to enable HAB closed mode

#UNCOMMENT line below to LOCK (CLOSE) HAB

#load fuse 0x00000002 > 0x06;

}

 

  1. Use Flashloader to burn the SW_GP2 (or SW_GP2 or OTPMK) fuses for AES128 key
  2. ..\blhost -t 50000 -u 0x15A2,0x0073 -j -- efuse-program-once 41 0c0d0e0f
  3. ..\blhost -t 50000 -u 0x15A2,0x0073 -j -- efuse-program-once 42 09000a0b
  4. ..\blhost -t 50000 -u 0x15A2,0x0073 -j -- efuse-program-once 43 05060708
  5. ..\blhost -t 50000 -u 0x15A2,0x0073 -j -- efuse-program-once 44 01020304
  6.  

 

  1. also using flash loader Burn BEE Key Selection fuse for Region 0

 

..\blhost.exe -t 50000 -u 0x15A2,0x0073 -j -- efuse-program-once 6 00003000

 

  1. Repeat this steps 2-4 if using region 1, if using sw_GP4 or if using OTPMK

 

  1. Use the Image_Enc code and MBED_TLS to encrypt your code using AES128-CTR.

            OR use OpenSLL as below:

 

    encryption method using openssl:

    openssl iv: (Nonce)

    bit 128:32 is unique value

        Top Address = 0x60008000 >> 4 = 0x6000800

    bit 31:0 is top address >> 4 ie: supplied by BEE during OTF decryption

    nonce unique value sample: 1234567890ABCDEF01020304 ie: 96bits

 

    iv/Nonce: 1234567890ABCDEF0102030406000800 ie: unique for each firmware encryption

    aesKey: 010203040506070809000A0B0C0D0E0F ie: aes128-ctr key which must be kept secret

 

    $ openssl enc -e -aes-128-ctr -nopad -nosalt -K 010203040506070809000A0B0C0D0E0F

        -iv 1234567890ABCDEF0102030406000800 -in FIRMWARE.BIN -out FIRMWARE_ENC.BIN

 

 

NOTE: I do not use BEE in boot mode. This means I do not use the EKIB and PRDB BEE data blocks.

            If you use image_enc, set ‘bootable_image=0’

            Also image_enc generates a random Nonce (counter) – It does a bad job. Either fix the random# generator

            Or specify your own in code.

 

My process is as follows:

            I have a HAB authenticated/encrypted boot loader which then programatically initializes the BEE

            And then authenticates the code decrypted by the BEE prior to executing it – Chain of Trust

 

            This process allows us to update the encrypted BEE area with an OTA firmware update.

            Exact details are confidential.

 

            Sample BEE init code:

 

#include "fsl_bee.h"

#include "main.h"

 

/*******************************************************************************

 * Definitions

 ******************************************************************************/

/* FAC region configuration registers */

#define REG0_START_ADDR_GPR GPR18

#define REG0_END_ADDR_GPR GPR19

#define REG0_DECRYPT_EN IOMUXC_GPR_GPR11_BEE_DE_RX_EN(1/* FlexSPI data decryption enabled for region-0 */

 

#define REG1_START_ADDR_GPR GPR20

#define REG1_END_ADDR_GPR GPR21

#define REG1_DECRYPT_EN IOMUXC_GPR_GPR11_BEE_DE_RX_EN(2/* FlexSPI data decryption enabled for region-1 */

 

#define REG2_START_ADDR_GPR GPR22

#define REG2_END_ADDR_GPR GPR23

#define REG2_DECRYPT_EN IOMUXC_GPR_GPR11_BEE_DE_RX_EN(3/* FlexSPI data decryption enabled for region-2 */

 

#define REG_DECRYPT_EN_GPR GPR11

#define AES_KEY_LEN 16

#define AES_NONCE_LEN 16

 

/* AES user key must be stored in little_endian. */

/* Development aesKey = 01020304_05060708_090A0B0C_0D0E0F10h */

static const uint8_t aesKey[] __attribute__((aligned)) = {0x0F0x0E0x0D0x0C0x0B0x0A0x000x09,

    0x080x070x060x050x040x030x020x01};

 

//static uint8_t aesNonce[] __attribute__((aligned)) = {0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0x02, 0x01,

//    0xEF, 0xCD, 0xAB, 0x90, 0x78, 0x56, 0x34, 0x12};

 

/* encryption method using openssl:

    openssl iv:

    bit 128:32 is unique value

        Top Address = 0x60008000 >> 4 = 0x6000800

    bit 31:0 is top address >> 4 ie: supplied by BEE during OTF decryption

    nonce unique value: 1234567890ABCDEF01020304 ie: 96bits

 

    iv:     1234567890ABCDEF0102030406000800 ie: unique for each firmware encryption

 

    aesKey: 010203040506070809000A0B0C0D0E0F ie: aes128-ctr key which must be kept secret

 

    $ openssl enc -e -aes-128-ctr -nopad -nosalt -K 010203040506070809000A0B0C0D0E0F

        -iv 1234567890ABCDEF0102030406000800 -in FIRMWARE.BIN -out FIRMWARE_ENC.BIN

*/

 

void BEE_SetRegionKeyValid(BEE_Type *basebee_region_t region)

{

    bool redisable = false;

 

    /* Wait until BEE is in idle state */

    while (0U == (BEE_GetStatusFlags(base) & (uint32_t)kBEE_IdleFlag)) {}

 

    /* Clear KEY_VALID bit before new key is loaded */

    base->CTRL &= ~BEE_CTRL_KEY_VALID_MASK;

 

    /* Enable BEE before key configuration. */

    if (0U == (base->CTRL & BEE_CTRL_BEE_ENABLE_MASK))

    {

        BEE_Enable(base);

        redisable = true;

    }

 

    if (region == kBEE_Region0)

    {

        base->CTRL &= ~BEE_CTRL_KEY_REGION_SEL_MASK;

    }

    else if (region == kBEE_Region1)

    {

        base->CTRL |= BEE_CTRL_KEY_REGION_SEL_MASK;

    }

 

    /* Set KEY_VALID bit to trigger key loading */

    base->CTRL |= BEE_CTRL_KEY_VALID_MASK;

    /* Wait until key is ready */

    while (0U == (base->CTRL & BEE_CTRL_KEY_VALID_MASK)) {}

 

    /* Redisable BEE if it was disabled before this function call. */

    if (redisable)

    {

        BEE_Disable(base);

    }

}

 

// Initialise BEE with Firmware Nonce and Candidate (New Firmware) Nonce

void init_bee(uint8_t aesNonceFW[]uint8_t aesNonceCND[])

{

    bee_region_config_t beeConfig;

    IOMUXC_GPR_Type *iomuxc = IOMUXC_GPR;

 

    /* Get default configuration. */

    BEE_GetDefaultConfig(&beeConfig);

 

    // NOTE: REGION 1 NOT USED

    /* Configure Start address and end address of access protected region-1 - Candidate*/

    //iomuxc->REG1_START_ADDR_GPR = (uint32_t)CANDIDATE_START_ADDR;

    //iomuxc->REG1_END_ADDR_GPR = (uint32_t)CANDIDATE_END_ADDR;

 

    /* Configure Start address and end address of access protected region-0 - Firmware*/

    iomuxc->REG0_START_ADDR_GPR = (uint32_t)FIRMWARE_START_ADDR;

    iomuxc->REG0_END_ADDR_GPR = (uint32_t)FIRMWARE_END_ADDR;

 

    /* Configure Start address and end address of access protected region-0 - Firmware*/

    //iomuxc->REG2_START_ADDR_GPR = (uint32_t)0x20000000;

    //iomuxc->REG2_END_ADDR_GPR = (uint32_t)0x20001000;

 

    //iomuxc->REG_DECRYPT_EN_GPR =  REG1_DECRYPT_EN;

    /* enable region x decryption */

    iomuxc->REG_DECRYPT_EN_GPR = iomuxc->REG_DECRYPT_EN_GPR | (REG0_DECRYPT_EN /*| REG1_DECRYPT_EN | REG2_DECRYPT_EN*/ );

 

    /* Init BEE driver and apply the configuration */

    BEE_Init(BEE);

    BEE_SetConfig(BEE, &beeConfig);

 

    //for region 1 set key to BEE register    

    //BEE_SetRegionKey(BEE, kBEE_Region1, aesKey, AES_KEY_LEN);

 

    //Set AES user key for region0 and 1. NOTE: BEE_KEY0_SEL fuse must be set to SW-GP2 eFuse.

    //BEE_SetRegionKeyValid(BEE, kBEE_Region1);

    BEE_SetRegionKeyValid(BEE, kBEE_Region0);

 

    //use supplied Nonce

    //BEE_SetRegionNonce(BEE, kBEE_Region1, aesNonceCND, AES_NONCE_LEN);

    BEE_SetRegionNonce(BEE, kBEE_Region0, aesNonceFW, AES_NONCE_LEN);

 

    /* Enable BEE decryption */

    BEE_Enable(BEE);

}

 

 

1,550 Views
kerryzhou
NXP TechSupport
NXP TechSupport

Your reply is good, thanks for your sharing.

0 Kudos

1,550 Views
john8
Contributor III

Thankyou.

I tried to download image_enc2 from Baidu. But I cannot because I am in Australia and I cannot signup fro an account.

Is there another way to download image_enc2?????

regards,

0 Kudos

1,550 Views
rshipman
Contributor V

Hi Tony,

We are looking at firmware upgrading in the field too. I would be interested in your solution.

With our current choice of flash (i.e. not read-while-write, same as eval board), we can only read from flash when XIP, erase/write is not possible. (Although I thought I had managed to get block erase to work.) So the solution may be to get your xip code to load some kind of 'update in field' util into SRAM (or elsewhere) and execute it. That util should be able to read/erase/write flash at will. In theory. Their demo of this works (when in SRAM): RT1020-EVK SDK flexspi/flash demo does not work?

Hope that helps.

Also I will email you the flow I use for loading an encrypted XIP binary without using the apps MfgTool (no USB) or NXP-MCUBootUtils.

Sorry if this is out of context - I have only skimmed read this posting and the replies.

Regards,

Ronnie

0 Kudos

1,550 Views
jeremi_jasinski
Contributor I

Hi Rshipman,

could you send me an email too?

I also have troubles in enabling BEE encryption mode, and to flow of loading encrypted XIP binary would be a god send.

Please send me an email: jeremi.jasinski@gc5.pl or post the flow here.

regards,

morali

0 Kudos

1,550 Views
john8
Contributor III

Hi, we have field update/OTA working using HAB and BEE.

Our flow simply copies a BEE encrypted HAB image to upper flash memory.

The bootloader simply verifys it using CRC, then copies to a lower flash area.

Then enables BEE, then authenticates using HAB. Then jumps to it...

regards,

0 Kudos

1,550 Views
t_thurgood
Contributor III

Hi Kerry,

Have you managed to investigate this operation?

Please advise.

best regards,

Tony

0 Kudos

1,562 Views
kerryzhou
NXP TechSupport
NXP TechSupport

Hi Tony Thurgood,

   Some updated information from our MCUBootUtility author.

1. RT1021 also support master key mode.

pastedImage_1.png

About the OTPMK register question, it should be the RT1020 RM problem, I will contact the doc department about it.

So, you can use the master key mode.

2. About user key engine mode.

   RT1020/RT1050 just can support single engine mode, it can support both engine0 and engine1 together.

Wish it helps you!

Have a great day,
Kerry

 

-------------------------------------------------------------------------------
Note:
- If this post answers your question, please click the "Mark Correct" button. Thank you!

 

- We are following threads for 7 weeks after the last post, later replies are ignored
Please open a new thread and refer to the closed one, if you have a related question at a later point in time.
-------------------------------------------------------------------------------

0 Kudos