From fc67c019952530c36a3605a50c37624773550b01 Mon Sep 17 00:00:00 2001 From: Laukik Hase Date: Fri, 27 Mar 2026 17:04:10 +0530 Subject: [PATCH] ci(esp_tee): Fix TEE test-suite failures with Secure Boot enabled --- .../tee_cli_app/sdkconfig.ci.release | 5 +- .../test_apps/tee_cli_app/sdkconfig.ci.sb_fe | 3 +- .../test_apps/tee_cli_app/sdkconfig.defaults | 3 + .../secure_boot_signing_key_ecdsa_p256.pem | 5 ++ ...m => secure_boot_signing_key_rsa_3072.pem} | 0 .../esp_tee/test_apps/tee_test_fw/conftest.py | 61 ++++++++--------- .../tee_test_fw/main/test_esp_tee_ota.c | 4 +- .../tee_test_fw/main/test_esp_tee_sec_stg.c | 4 ++ .../tee_test_fw/pytest_esp_tee_ut.py | 64 +++++++++++------- .../test_apps/tee_test_fw/sdkconfig.defaults | 3 + .../test_keys/tee_sec_stg_hmac_key.bin | Bin 0 -> 32 bytes 11 files changed, 88 insertions(+), 64 deletions(-) create mode 100644 components/esp_tee/test_apps/tee_cli_app/test_keys/secure_boot_signing_key_ecdsa_p256.pem rename components/esp_tee/test_apps/tee_cli_app/test_keys/{secure_boot_signing_key.pem => secure_boot_signing_key_rsa_3072.pem} (100%) create mode 100644 components/esp_tee/test_apps/tee_test_fw/test_keys/tee_sec_stg_hmac_key.bin diff --git a/components/esp_tee/test_apps/tee_cli_app/sdkconfig.ci.release b/components/esp_tee/test_apps/tee_cli_app/sdkconfig.ci.release index 873c025eb1..c8a6732f48 100644 --- a/components/esp_tee/test_apps/tee_cli_app/sdkconfig.ci.release +++ b/components/esp_tee/test_apps/tee_cli_app/sdkconfig.ci.release @@ -12,7 +12,8 @@ CONFIG_SECURE_TEE_SEC_STG_EFUSE_HMAC_KEY_ID=5 CONFIG_SECURE_TEE_EXT_FLASH_MEMPROT_SPI1=n # Secure Boot -CONFIG_PARTITION_TABLE_OFFSET=0xf000 +CONFIG_PARTITION_TABLE_OFFSET=0xF000 CONFIG_SECURE_BOOT=y +CONFIG_SECURE_SIGNED_APPS_ECDSA_V2_SCHEME=y CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES=y -CONFIG_SECURE_BOOT_SIGNING_KEY="test_keys/secure_boot_signing_key.pem" +CONFIG_SECURE_BOOT_SIGNING_KEY="test_keys/secure_boot_signing_key_ecdsa_p256.pem" diff --git a/components/esp_tee/test_apps/tee_cli_app/sdkconfig.ci.sb_fe b/components/esp_tee/test_apps/tee_cli_app/sdkconfig.ci.sb_fe index bdec94c11b..db9a7be318 100644 --- a/components/esp_tee/test_apps/tee_cli_app/sdkconfig.ci.sb_fe +++ b/components/esp_tee/test_apps/tee_cli_app/sdkconfig.ci.sb_fe @@ -3,8 +3,9 @@ CONFIG_PARTITION_TABLE_OFFSET=0xf000 # Secure Boot CONFIG_SECURE_BOOT=y +CONFIG_SECURE_SIGNED_APPS_ECDSA_V2_SCHEME=y CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES=y -CONFIG_SECURE_BOOT_SIGNING_KEY="test_keys/secure_boot_signing_key.pem" +CONFIG_SECURE_BOOT_SIGNING_KEY="test_keys/secure_boot_signing_key_ecdsa_p256.pem" # Flash Encryption CONFIG_SECURE_FLASH_ENC_ENABLED=y diff --git a/components/esp_tee/test_apps/tee_cli_app/sdkconfig.defaults b/components/esp_tee/test_apps/tee_cli_app/sdkconfig.defaults index c35d9943e4..a0ae715a92 100644 --- a/components/esp_tee/test_apps/tee_cli_app/sdkconfig.defaults +++ b/components/esp_tee/test_apps/tee_cli_app/sdkconfig.defaults @@ -20,3 +20,6 @@ CONFIG_EXAMPLE_OTA_RECV_TIMEOUT=30000 CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN=y CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE=y CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE_PATH="test_certs/server_cert.pem" + +# Takes effect only when Secure boot is enabled +CONFIG_SECURE_BOOT_FLASH_BOOTLOADER_DEFAULT=y diff --git a/components/esp_tee/test_apps/tee_cli_app/test_keys/secure_boot_signing_key_ecdsa_p256.pem b/components/esp_tee/test_apps/tee_cli_app/test_keys/secure_boot_signing_key_ecdsa_p256.pem new file mode 100644 index 0000000000..e9dd586325 --- /dev/null +++ b/components/esp_tee/test_apps/tee_cli_app/test_keys/secure_boot_signing_key_ecdsa_p256.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIFFwmnckyThKZQMV40ceAQm8OxwP1aI0dvWt3P9/4VAgoAoGCCqGSM49 +AwEHoUQDQgAEwMObAE6S2QjA4vYnifYGDO/Jd9Pr9p2CWKxQVTsziuqz2pJxzjcQ +zJT6Aj30auml+oIGvNwBnhoZ3v5SCyzqOw== +-----END EC PRIVATE KEY----- diff --git a/components/esp_tee/test_apps/tee_cli_app/test_keys/secure_boot_signing_key.pem b/components/esp_tee/test_apps/tee_cli_app/test_keys/secure_boot_signing_key_rsa_3072.pem similarity index 100% rename from components/esp_tee/test_apps/tee_cli_app/test_keys/secure_boot_signing_key.pem rename to components/esp_tee/test_apps/tee_cli_app/test_keys/secure_boot_signing_key_rsa_3072.pem diff --git a/components/esp_tee/test_apps/tee_test_fw/conftest.py b/components/esp_tee/test_apps/tee_test_fw/conftest.py index 77ed8394c7..2a2de9e4f3 100644 --- a/components/esp_tee/test_apps/tee_test_fw/conftest.py +++ b/components/esp_tee/test_apps/tee_test_fw/conftest.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # pylint: disable=W0621 # redefined-outer-name import base64 @@ -202,25 +202,6 @@ class TEESerial(IdfSerial): def _get_flash_size(self) -> Any: return self.app.sdkconfig.get('ESPTOOLPY_FLASHSIZE', '') - @EspSerial.use_esptool() - def bootloader_force_flash_if_req(self) -> None: - # Forcefully flash the bootloader only if security features are enabled - if any( - ( - self.app.sdkconfig.get('SECURE_BOOT', True), - self.app.sdkconfig.get('SECURE_FLASH_ENC_ENABLED', True), - ) - ): - offs = int(self.app.sdkconfig.get('BOOTLOADER_OFFSET_IN_FLASH', 0)) - bootloader_path = os.path.join(self.app.binary_path, 'bootloader', 'bootloader.bin') - encrypt = '--encrypt' if self.app.sdkconfig.get('SECURE_FLASH_ENC_ENABLED') else '' - flash_size = self._get_flash_size() - - esptool.main( - f'--no-stub write-flash {offs} {bootloader_path} --force {encrypt} --flash-size {flash_size}'.split(), - esp=self.esp, - ) - @EspSerial.use_esptool() def custom_erase_partition(self, partition: str) -> None: if self.app.sdkconfig.get('SECURE_ENABLE_SECURE_ROM_DL_MODE'): @@ -294,26 +275,18 @@ class TEESerial(IdfSerial): if os.path.exists(file): os.remove(file) - @EspSerial.use_esptool() - def custom_flash(self) -> None: - self.bootloader_force_flash_if_req() - self.flash() - @EspSerial.use_esptool() def custom_flash_w_test_tee_img_gen(self) -> None: - self.bootloader_force_flash_if_req() self.flash() self.copy_test_tee_img('ota_1', False) @EspSerial.use_esptool() def custom_flash_w_test_tee_img_rb(self) -> None: - self.bootloader_force_flash_if_req() self.flash() self.copy_test_tee_img('ota_1', True) @EspSerial.use_esptool() def custom_flash_with_empty_sec_stg(self) -> None: - self.bootloader_force_flash_if_req() self.flash() self.custom_erase_partition('secure_storage') @@ -354,12 +327,11 @@ class TEESerial(IdfSerial): }, ] - NVS_KEYS_B64 = 'MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzPMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzA==' - + TEST_KEYS_DIR = Path(__file__).parent / 'test_keys' TMP_DIR = Path('tmp') - NVS_KEYS_PATH = TMP_DIR / 'nvs_keys.bin' NVS_CSV_PATH = TMP_DIR / 'tee_sec_stg_val.csv' NVS_BIN_PATH = TMP_DIR / 'tee_sec_stg_nvs.bin' + NVS_KEYS_FILE = 'tee_sec_stg_nvs_keys.bin' def run_command(self, command: list[str]) -> None: try: @@ -391,7 +363,6 @@ class TEESerial(IdfSerial): input_path = tmp_dir / entry['input'] self.write_keys_to_file(entry['b64'], input_path) entry['input'] = str(input_path) - self.write_keys_to_file(self.NVS_KEYS_B64, self.NVS_KEYS_PATH) idf_path = Path(os.environ['IDF_PATH']) ESP_TEE_SEC_STG_KEYGEN = os.path.join( @@ -401,6 +372,30 @@ class TEESerial(IdfSerial): idf_path, 'components', 'nvs_flash', 'nvs_partition_generator', 'nvs_partition_gen.py' ) + nvs_keys = tmp_dir / self.NVS_KEYS_FILE + if self.app.sdkconfig.get('SECURE_TEE_SEC_STG_MODE_RELEASE'): + hmac_key_src = self.TEST_KEYS_DIR / 'tee_sec_stg_hmac_key.bin' + self.run_command( + [ + sys.executable, + NVS_PARTITION_GEN, + 'generate-key', + '--key_protect_hmac', + '--kp_hmac_inputkey', + str(hmac_key_src), + '--keyfile', + self.NVS_KEYS_FILE, + '--outdir', + str(tmp_dir), + ] + ) + nvs_keys = tmp_dir / 'keys' / self.NVS_KEYS_FILE + else: + NVS_KEYS_DEV_B64 = ( + 'MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzPMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzA==' + ) + self.write_keys_to_file(NVS_KEYS_DEV_B64, nvs_keys) + cmds = [ [sys.executable, ESP_TEE_SEC_STG_KEYGEN, '-k', entry['type'], '-o', str(tmp_dir / f'{entry["key"]}.bin')] + (['-i', entry['input']] if entry['input'] else []) @@ -410,7 +405,6 @@ class TEESerial(IdfSerial): csv_path = self.create_tee_sec_stg_csv(tmp_dir) nvs_bin = self.NVS_BIN_PATH - nvs_keys = self.NVS_KEYS_PATH size = self.app.partition_table['secure_storage']['size'] cmds.append( @@ -430,7 +424,6 @@ class TEESerial(IdfSerial): for cmd in cmds: self.run_command(cmd) - self.bootloader_force_flash_if_req() self.flash() self.custom_erase_partition('secure_storage') self.custom_write_partition('secure_storage', nvs_bin) diff --git a/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_ota.c b/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_ota.c index 6096e49ac3..5e305af383 100644 --- a/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_ota.c +++ b/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_ota.c @@ -127,8 +127,8 @@ TEST_CASE("Test TEE OTA - Corrupted image", "[ota_neg_2]") /* Corrupting the image */ ESP_LOGI(TAG, "Corrupting the image at some offset..."); uint32_t corrupt[8] = {[0 ... 7] = 0x0BADC0DE}; - curr_write_offset -= (2 * FLASH_SECTOR_SIZE + sizeof(corrupt)); - TEST_ESP_OK(esp_tee_ota_write(curr_write_offset, (const void *)corrupt, sizeof(corrupt))); + uint32_t offs = SOC_MMU_PAGE_SIZE + 0x200; + TEST_ESP_OK(esp_tee_ota_write(offs, (const void *)corrupt, sizeof(corrupt))); TEST_ESP_ERR(ESP_ERR_IMAGE_INVALID, esp_tee_ota_end()); } diff --git a/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_sec_stg.c b/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_sec_stg.c index 4d8f2dcdf7..1cd421ddb9 100644 --- a/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_sec_stg.c +++ b/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_sec_stg.c @@ -425,6 +425,10 @@ static void do_ecdsa_sign_and_verify(const esp_tee_sec_storage_key_cfg_t *cfg, c TEST_ESP_OK(verify_ecdsa_sign(cfg->type, digest, digest_len, &pubkey, &sign)); } +/* NOTE: In release mode (CONFIG_SECURE_TEE_SEC_STG_MODE_RELEASE), the test expects + * the eFuse-burned HMAC key used for TEE secure storage to be available at + * the path "test_keys/tee_sec_stg_hmac_key.bin" + */ TEST_CASE("Test TEE Secure Storage - Host-generated keys", "[sec_storage_host_keygen]") { const char *aes_key_ids[] = { "aes256_key0", "aes256_key1" }; diff --git a/components/esp_tee/test_apps/tee_test_fw/pytest_esp_tee_ut.py b/components/esp_tee/test_apps/tee_test_fw/pytest_esp_tee_ut.py index ad46a930a2..e2f8bba286 100644 --- a/components/esp_tee/test_apps/tee_test_fw/pytest_esp_tee_ut.py +++ b/components/esp_tee/test_apps/tee_test_fw/pytest_esp_tee_ut.py @@ -6,7 +6,7 @@ from enum import Enum import pytest from pytest_embedded_idf import IdfDut from pytest_embedded_idf.utils import idf_parametrize -from tee_exception_cfg import TEE_EXCEPTION_TEST_MAP +from tee_exception_test_map import TEE_EXCEPTION_TEST_MAP # ---------------- Pytest build parameters ---------------- @@ -19,6 +19,12 @@ CONFIG_DEFAULT = [ ] CONFIG_OTA = [ + # 'config, target, markers', + ('tee_ota', target, (pytest.mark.generic,)) + for target in TESTING_TARGETS +] + +CONFIG_OTA_NO_AUTOFLASH = [ # 'config, target, skip_autoflash, markers', ('tee_ota', target, 'y', (pytest.mark.generic,)) for target in TESTING_TARGETS @@ -49,8 +55,10 @@ def test_esp_tee(dut: IdfDut) -> None: CONFIG_ALL, indirect=['config', 'target'], ) -@pytest.mark.skipif(targets=['esp32c61'], reason='Not supported') def test_esp_tee_crypto_aes(dut: IdfDut) -> None: + if dut.target == 'esp32c61': + pytest.skip(f'AES not supported on {dut.target}') + dut.run_all_single_board_cases(group='aes') dut.run_all_single_board_cases(group='aes-gcm') @@ -71,8 +79,10 @@ def test_esp_tee_crypto_sha(dut: IdfDut) -> None: CONFIG_ALL, indirect=['config', 'target'], ) -@pytest.mark.skipif(targets=['esp32c61'], reason='Not supported') def test_esp_tee_aes_perf(dut: IdfDut) -> None: + if dut.target == 'esp32c61': + pytest.skip(f'AES not supported on {dut.target}') + for i in range(10): dut.run_all_single_board_cases(name=['mbedtls AES performance']) @@ -237,8 +247,6 @@ def run_flash_access_test(dut: IdfDut, api: TeeFlashAccessApi, test_name: str) - # Panics are expected during these tests dut.skip_decode_panic = True - dut.serial.custom_flash() - extra_data = dut._parse_test_menu() test_case = next((tc for tc in extra_data if tc.name == test_name), None) @@ -249,9 +257,9 @@ def run_flash_access_test(dut: IdfDut, api: TeeFlashAccessApi, test_name: str) - @idf_parametrize( - 'config, target, skip_autoflash, markers', + 'config, target, markers', CONFIG_OTA, - indirect=['config', 'target', 'skip_autoflash'], + indirect=['config', 'target'], ) def test_esp_tee_flash_prot_esp_partition_mmap(dut: IdfDut) -> None: run_flash_access_test( @@ -260,9 +268,9 @@ def test_esp_tee_flash_prot_esp_partition_mmap(dut: IdfDut) -> None: @idf_parametrize( - 'config, target, skip_autoflash, markers', + 'config, target, markers', CONFIG_OTA, - indirect=['config', 'target', 'skip_autoflash'], + indirect=['config', 'target'], ) def test_esp_tee_flash_prot_spi_flash_mmap(dut: IdfDut) -> None: run_flash_access_test( @@ -271,9 +279,9 @@ def test_esp_tee_flash_prot_spi_flash_mmap(dut: IdfDut) -> None: @idf_parametrize( - 'config, target, skip_autoflash, markers', + 'config, target, markers', CONFIG_OTA, - indirect=['config', 'target', 'skip_autoflash'], + indirect=['config', 'target'], ) def test_esp_tee_flash_prot_esp_rom_spiflash(dut: IdfDut) -> None: run_flash_access_test( @@ -282,18 +290,18 @@ def test_esp_tee_flash_prot_esp_rom_spiflash(dut: IdfDut) -> None: @idf_parametrize( - 'config, target, skip_autoflash, markers', + 'config, target, markers', CONFIG_OTA, - indirect=['config', 'target', 'skip_autoflash'], + indirect=['config', 'target'], ) def test_esp_tee_flash_prot_esp_partition(dut: IdfDut) -> None: run_flash_access_test(dut, TeeFlashAccessApi.ESP_PARTITION, 'Test REE-TEE isolation: Flash - SPI1 (esp_partition)') @idf_parametrize( - 'config, target, skip_autoflash, markers', + 'config, target, markers', CONFIG_OTA, - indirect=['config', 'target', 'skip_autoflash'], + indirect=['config', 'target'], ) def test_esp_tee_flash_prot_esp_flash(dut: IdfDut) -> None: run_flash_access_test(dut, TeeFlashAccessApi.ESP_FLASH, 'Test REE-TEE isolation: Flash - SPI1 (esp_flash)') @@ -302,9 +310,11 @@ def test_esp_tee_flash_prot_esp_flash(dut: IdfDut) -> None: # ---------------- TEE Local OTA tests ---------------- -@pytest.mark.generic -@idf_parametrize('config', ['tee_ota'], indirect=['config']) -@idf_parametrize('target', TESTING_TARGETS, indirect=['target']) +@idf_parametrize( + 'config, target, markers', + CONFIG_OTA, + indirect=['config', 'target'], +) def test_esp_tee_ota_negative(dut: IdfDut) -> None: # start test dut.run_all_single_board_cases(group='ota_neg_1', timeout=10) @@ -312,7 +322,7 @@ def test_esp_tee_ota_negative(dut: IdfDut) -> None: @idf_parametrize( 'config, target, skip_autoflash, markers', - CONFIG_OTA, + CONFIG_OTA_NO_AUTOFLASH, indirect=['config', 'target', 'skip_autoflash'], ) def test_esp_tee_ota_corrupted_img(dut: IdfDut) -> None: @@ -346,7 +356,7 @@ def tee_ota_stage_checks(dut: IdfDut, stage: TeeOtaStage, offset: str) -> None: @idf_parametrize( 'config, target, skip_autoflash, markers', - CONFIG_OTA, + CONFIG_OTA_NO_AUTOFLASH, indirect=['config', 'target', 'skip_autoflash'], ) def test_esp_tee_ota_reboot_without_ota_end(dut: IdfDut) -> None: @@ -369,7 +379,7 @@ def test_esp_tee_ota_reboot_without_ota_end(dut: IdfDut) -> None: @idf_parametrize( 'config, target, skip_autoflash, markers', - CONFIG_OTA, + CONFIG_OTA_NO_AUTOFLASH, indirect=['config', 'target', 'skip_autoflash'], ) def test_esp_tee_ota_valid_img(dut: IdfDut) -> None: @@ -400,7 +410,7 @@ def test_esp_tee_ota_valid_img(dut: IdfDut) -> None: @idf_parametrize( 'config, target, skip_autoflash, markers', - CONFIG_OTA, + CONFIG_OTA_NO_AUTOFLASH, indirect=['config', 'target', 'skip_autoflash'], ) def test_esp_tee_ota_rollback(dut: IdfDut) -> None: @@ -439,7 +449,7 @@ def test_esp_tee_ota_rollback(dut: IdfDut) -> None: @idf_parametrize( 'config, target, skip_autoflash, markers', - CONFIG_OTA, + CONFIG_OTA_NO_AUTOFLASH, indirect=['config', 'target', 'skip_autoflash'], ) def test_esp_tee_secure_storage(dut: IdfDut) -> None: @@ -451,11 +461,15 @@ def test_esp_tee_secure_storage(dut: IdfDut) -> None: @idf_parametrize( 'config, target, skip_autoflash, markers', - CONFIG_OTA, + CONFIG_OTA_NO_AUTOFLASH, indirect=['config', 'target', 'skip_autoflash'], ) def test_esp_tee_secure_storage_with_host_img(dut: IdfDut) -> None: # Flash image and write the secure_storage partition with host-generated keys + + # NOTE: In release mode (CONFIG_SECURE_TEE_SEC_STG_MODE_RELEASE), the test + # expects the eFuse-burned HMAC key used for TEE secure storage to be available + # at the path "test_keys/tee_sec_stg_hmac_key.bin" dut.serial.custom_flash_with_host_gen_sec_stg_img() dut.run_all_single_board_cases(group='sec_storage_host_keygen') @@ -466,7 +480,7 @@ def test_esp_tee_secure_storage_with_host_img(dut: IdfDut) -> None: @idf_parametrize( 'config, target, skip_autoflash, markers', - CONFIG_OTA, + CONFIG_OTA_NO_AUTOFLASH, indirect=['config', 'target', 'skip_autoflash'], ) def test_esp_tee_attestation(dut: IdfDut) -> None: diff --git a/components/esp_tee/test_apps/tee_test_fw/sdkconfig.defaults b/components/esp_tee/test_apps/tee_test_fw/sdkconfig.defaults index 4b67d45b71..7e6d5c6bbd 100644 --- a/components/esp_tee/test_apps/tee_test_fw/sdkconfig.defaults +++ b/components/esp_tee/test_apps/tee_test_fw/sdkconfig.defaults @@ -14,3 +14,6 @@ CONFIG_PARTITION_TABLE_OFFSET=0xF000 # TEE IRAM size CONFIG_SECURE_TEE_IRAM_SIZE=0xC400 + +# Takes effect only when Secure boot is enabled +CONFIG_SECURE_BOOT_FLASH_BOOTLOADER_DEFAULT=y diff --git a/components/esp_tee/test_apps/tee_test_fw/test_keys/tee_sec_stg_hmac_key.bin b/components/esp_tee/test_apps/tee_test_fw/test_keys/tee_sec_stg_hmac_key.bin new file mode 100644 index 0000000000000000000000000000000000000000..9868f801a9a3230283e0936b52fe70a9f68a224c GIT binary patch literal 32 qcmV+*0N?*XQ|)j$@tuKmvmZRW+ywyCJN^EZ^%Yu2yrZ}J%95%Oo)NeJ literal 0 HcmV?d00001