1. ESP32 Secure Boot 기본개념
- ARM/ESP32 Secure Boot 관련비교 내용
반드시 이해를 해야하며, 이를 이해해야 아래의 내용도 이해가능
- ESP32의 Startup Boot Flow
- 1st stage bootloader : ESP32 내부에 존재하는 Bootloader로 ROM에 이미 저장
- 2nd stage bootloader: ESP32의 SPI Flash 저장되어진 실제적인 bootloader image 이며 Partition table 접근가능
- application image: app image로 factory or ota 영역에 저장되는 application image
상위와 같이 ESP32는 Booting하며, User가 신경써야 할 부분은 2nd Stage 와 Application image이다.
1.1 ESP32 Secure Boot Version
Espressif에서는 Secure Boot V1, V2을 제공하고 있지만, V2 더 권장하고 있으며, 다음과 같은 차이가 존재한다.
- SecureBoot v1 : ESP32 Silicon Version1/2에서 지원가능
- SecureBoot v2 : ESP32 Silicon Version3에서 지원가능
- ESP32 Secure Boot v1 for ESP32 Revision1/2
- AES-256-ECB 방식으로 bootloader Encoding/Decoding
- ECSDA Signature 기반으로 appImage Signing/Verification Image검증
- OTP(eFUSE) 의 Block 2에 AES 256 bit 저장이용
- OTP(eFUSE) 의 ABS_DONE_0 설정
- ESP32 Secure Boot v2 for ESP32 Revision3
- RSA3072 Key 기반으로 Signature 기반 bootloader Signing/Verification Image검증
- RSA3072 Key 기반으로 Signature 기반 appImage Signing/Verification Image검증
- OTP(eFUSE) 의 Block 2에 SHA-256 bit digest 이용하여 RSA3072 Public Key 검증
- OTP(eFUSE) 의 ABS_DONE_1 설정
1.2 Secureboot 의 Signing 과 Verfication 이해
Secure Boot의 기본개념인 Signing 과 Verfication을 알아보도록 하자
흔히, 비대칭키를 이용하여 Sign을 하여, Signature 생성하고, 이를 검증(Verfication)하는 것이다.
Android System의 Sign 과 Vefication 이해
암호화 기본개념(비대칭키 와 대칭키 이외 기본개념)
ECDSA 기반의 Key 와 Certificate 세부분석
- Secure Boot Signing
- 비대칭키 기반 사용 (RSA,ECDSA)
- Build를 한 후 image를 생성할때 Private Key로 Signature를 생성
- Secure Boot Verification
- 비대칭키 기반 사용 (RSA,ECDSA)
- 이미 생성된 Signature를 Public Key를 이용하여 이를 검증
2. ESP32 Secure Boot 설정 과 진행
우선 Signature를 만들기 위해서, 즉 Signing을 위한 Key가 필요하다.
2.1 RSA Signing Key 생성방법
Window용 ESP-IDF가 버그가 있어 아래와 같이 export.bat 수정하도록 한다.
아래는 Powershell 용이 아니라 Command 용이므로 참고하도록하자.
- ESP-IDF 버그 수정 (v.4.2.3)
수정후 espsecure.py 사용가능
cd %userprofile%\esp\esp-idf\export.bat //수정 export.bat (Window에서 espsecure.py 미지원) .... DOSKEY idf.py=python.exe "%IDF_PATH%\tools\idf.py" $* DOSKEY esptool.py=python.exe "%IDF_PATH%\components\esptool_py\esptool\esptool.py" $* DOSKEY espefuse.py=python.exe "%IDF_PATH%\components\esptool_py\esptool\espefuse.py" $* DOSKEY espsecure.py=python.exe "%IDF_PATH%\components\esptool_py\esptool\espsecure.py" $* DOSKEY otatool.py=python.exe "%IDF_PATH%\components\app_update\otatool.py" $* DOSKEY parttool.py=python.exe "%IDF_PATH%\components\partition_table\parttool.py" $* echo Checking if Python packages are up to date... python.exe "%IDF_PATH%\tools\check_python_dependencies.py" if %errorlevel% neq 0 goto :end echo. echo Done! You can now compile ESP-IDF projects. echo Go to the project directory and run: echo. echo idf.py build echo. ....
RSA Signing Key를 꼭 espsecure.py로 생성할 필요는 없으며, openssl로 쉽게 생성가능.
- RSA3072 Key 생성방법-A
$ openssl genrsa -out my_secure_boot_signing_key.pem 3072 $ cp ./my_secure_boot_signing_key.pem /mnt/d/Works/
- RSA3072 Key 생성방법-B
espsecure.py 이용하여 쉽게 생성가능하며, 각 Version 별로 설정가능
$ espsecure.py generate_signing_key -v2 test1.pem $ espsecure.py generate_signing_key -v1 test1.pem $ espsecure.py sign_data --version 2 --keyfile my_secure_boot_signing_key.pem --output ./build/app_sign.bin ./build/app.bin
- ESP SecureBoot Version1
ESP32의 경우, Revision3 부터 사용권장
PEM 방식으로 ECDSA Signing/Verification 동작
ESP32 SecureBootV1 Manual
- ESP SecureBoot Version2
이를 좀 더 권장하며, ESP32 ECO3 이상에서 사용가능하며, RSA-PSS 방식이라고 말한다.
PEM 의 RSA3072 기반으로 Signing/Verfication 방식
OTP(eFUSE, One Time Programming) Block2 의 Secure Boot에 RSA Public Key의 Hash를 넣는 구조
ESP32 SecureBootV2 Manual
Secure Boot (ESP32 와 일반 ARM) 비교
- ESP32 와 외부 Security Chip 사용
ESP32에서 ECDSA의 Sign/Verify를 사용하기 위해서 외부 Chip를 사용
이전에 설명한 CMVP/KCMVP 기능이라고 생각하면 될 것 같으며, 확장기능으로 이를 이용하여 TLS도 가능하다
2.2 OTP(eFuse) 구조 와 역할
ESP32 의 경우, 1024bit 의 OTP(One Time Programmable, eFUSE)를 가지고 있고, 각 Block은 256bit로 총 4개로 나누어 분리하여 사용하도록 한다.
주의 해야할 것은 OTP(One Time Programmable) 한 번만 Write가 가능하므로, 신중하게 하도록하자.
실패하면, 쓸모가 없어진다.
- OTP(eFUSE) 구조
- OTP(One Time Programable,eFuse) Manual
- ESP32 burn-efuse 설명
- ESP32 OTP 사용방법
설정하는 방법은 현재 Command 중심으로 하는게 제일 편하며, ESP-IDF 개발환경에서 VS Code용 EFUSE Exploerer를 별도로 제공을 해주고 있다.
- UI 중심: VS Code EFUSE Exploerer
- Command 중심 아래 참조
2.3 OTP(eFuse) Command 기반 사용법
실제로 ESP32에서 제공하는 eFuse, OTP 설정값들을 확인해보고, 제어를 해보도록하자.
- Window에서 ESP-IDF 활성화
ESP-IDF가 현재 venv기반의 Python으로 되어있으므로 이를 활성화 부터 해야한다.
Command로 할 경우,
%userprofile%\.espressif\python_env\idf4.2_py3.8_env\Scripts\activate.bat %userprofile%\esp\esp-idf\export.bat
PowerShell 할 경우,
$Env:userprofile\.espressif\python_env\idf4.2_py3.8_env\Scripts\activate.ps1 $Env:userprofile\esp\esp-idf\export.ps1
Window 용 PowerShell 관련내용
주의, Powershell의 경우 Version 따라 동작이 많이 다르며, 점차 Linux 처럼 변경 중
- idf.py show_efuse_table (각 OTP 내부구성파악)
OTP인 eFuse에 각 Table 구성과 Field name을 쉽게 파악가능하다.
(idf4.2_py3.8_env) D:\Works\Project\>idf.py show_efuse_table //ESP Project 내에서 실행하며, 각 Field name 파악 Executing action: show_efuse_table Running ninja in directory d:\works\project\mod610_sw\build Executing "ninja show_efuse_table"... [1/1] cmd.exe /C "cd /D D:\Works\Project\mod610_sw\build\esp-idf\efus...ts/efuse/esp32/esp_efuse_table.csv -t esp32 --max_blk_len 192 --info" Parsing efuse CSV input file C:/Users/jhlee/esp/esp-idf/components/efuse/esp32/esp_efuse_table.csv ... Verifying efuse table... Max number of bits in BLK 192 Sorted efuse table: # field_name efuse_block bit_start bit_count //Field name 과 Block 위치 와 Bit Start 파악 1 WR_DIS_FLASH_CRYPT_CNT EFUSE_BLK0 2 1 2 WR_DIS_BLK1 EFUSE_BLK0 7 1 3 WR_DIS_BLK2 EFUSE_BLK0 8 1 4 WR_DIS_BLK3 EFUSE_BLK0 9 1 5 RD_DIS_BLK1 EFUSE_BLK0 16 1 6 RD_DIS_BLK2 EFUSE_BLK0 17 1 7 RD_DIS_BLK3 EFUSE_BLK0 18 1 8 FLASH_CRYPT_CNT EFUSE_BLK0 20 7 9 UART_DOWNLOAD_DIS EFUSE_BLK0 27 1 10 MAC_FACTORY EFUSE_BLK0 32 8 11 MAC_FACTORY EFUSE_BLK0 40 8 12 MAC_FACTORY EFUSE_BLK0 48 8 13 MAC_FACTORY EFUSE_BLK0 56 8 14 MAC_FACTORY EFUSE_BLK0 64 8 15 MAC_FACTORY EFUSE_BLK0 72 8 16 MAC_FACTORY_CRC EFUSE_BLK0 80 8 17 CHIP_VER_DIS_APP_CPU EFUSE_BLK0 96 1 18 CHIP_VER_DIS_BT EFUSE_BLK0 97 1 19 CHIP_VER_PKG EFUSE_BLK0 105 3 20 CHIP_CPU_FREQ_LOW EFUSE_BLK0 108 1 21 CHIP_CPU_FREQ_RATED EFUSE_BLK0 109 1 22 CHIP_VER_REV1 EFUSE_BLK0 111 1 23 ADC_VREF_AND_SDIO_DREF EFUSE_BLK0 136 6 24 XPD_SDIO_REG EFUSE_BLK0 142 1 25 SDIO_TIEH EFUSE_BLK0 143 1 26 SDIO_FORCE EFUSE_BLK0 144 1 27 CHIP_VER_REV2 EFUSE_BLK0 180 1 28 ENCRYPT_CONFIG EFUSE_BLK0 188 4 29 CONSOLE_DEBUG_DISABLE EFUSE_BLK0 194 1 30 ABS_DONE_0 EFUSE_BLK0 196 1 31 DISABLE_JTAG EFUSE_BLK0 198 1 32 DISABLE_DL_ENCRYPT EFUSE_BLK0 199 1 33 DISABLE_DL_DECRYPT EFUSE_BLK0 200 1 34 DISABLE_DL_CACHE EFUSE_BLK0 201 1 35 ENCRYPT_FLASH_KEY EFUSE_BLK1 0 192 36 SECURE_BOOT_KEY EFUSE_BLK2 0 192 37 MAC_CUSTOM_CRC EFUSE_BLK3 0 8 38 MAC_CUSTOM EFUSE_BLK3 8 48 39 ADC1_TP_LOW EFUSE_BLK3 96 7 40 ADC1_TP_HIGH EFUSE_BLK3 103 9 41 ADC2_TP_LOW EFUSE_BLK3 112 7 42 ADC2_TP_HIGH EFUSE_BLK3 119 9 43 SECURE_VERSION EFUSE_BLK3 128 32 44 MAC_CUSTOM_VER EFUSE_BLK3 184 8 Used bits in efuse table: EFUSE_BLK0 [2 2] [7 9] [16 18] [20 27] [32 87] [96 97] [105 109] [111 111] [136 144] [180 180] [188 191] [194 194] [196 196] [198 201] EFUSE_BLK1 [0 191] EFUSE_BLK2 [0 191] EFUSE_BLK3 [0 55] [96 159] [184 191] Note: Not printed ranges are free for using. (bits in EFUSE_BLK0 are reserved for Espressif)
- espefuse.py 사용법 (OTP 제어방법 확인)
아래와 같이 기본사용법을 확인을 반드시 하도록하며, 각 명령어에 따라 사용하도록하자.
(idf4.2_py3.8_env) D:\Works\Project> espefuse.py //esefuse 사용법확인 espefuse.py v3.1-dev usage: espefuse.py [-h] [--chip {auto,esp32,esp32s2,esp32s3beta2,esp32s3beta3,esp32c3}] [--baud BAUD] [--port PORT] [--before {default_reset,no_reset,esp32r1,no_reset_no_sync}] [--debug] [--virt] [--path-efuse-file PATH_EFUSE_FILE] [--do-not-confirm] {burn_efuse,read_protect_efuse,write_protect_efuse,burn_block_data,burn_bit,adc_info,dump,summary,burn_key,burn_key_digest,set_flash_voltage,burn_custom_mac,get_custom_mac} ... positional arguments: {burn_efuse,read_protect_efuse,write_protect_efuse,burn_block_data,burn_bit,adc_info,dump,summary,burn_key,burn_key_digest,set_flash_voltage,burn_custom_mac,get_custom_mac} Run espefuse.py {command} -h for additional help burn_efuse Burn the efuse with the specified name //Feild name 기반 Write 기능, burn_bit 와 동일 read_protect_efuse Disable readback for the efuse with the specified name write_protect_efuse Disable writing to the efuse with the specified name burn_block_data Burn non-key data to EFUSE blocks. (Don't use this command to burn key data for Flash Encryption or ESP32 Secure Boot V1, as the byte order of keys is swapped (use burn_key)). burn_bit Burn bit in the efuse block. //burn_efuse 거의 동일하며, Block 과 Bit를 입력하여 Write adc_info Display information about ADC calibration data stored in efuse. dump Dump raw hex values of all efuses summary Print human-readable summary of efuse values burn_key Burn a 256-bit key to EFUSE: BLOCK1, flash_encryption, BLOCK2, secure_boot_v1, secure_boot_v2, BLOCK3 burn_key_digest Parse a RSA public key and burn the digest to eFuse for use with Secure Boot V2 set_flash_voltage Permanently set the internal flash voltage regulator to either 1.8V, 3.3V or OFF. This means GPIO12 can be high or low at reset without changing the flash voltage. burn_custom_mac Burn a 48-bit Custom MAC Address to EFUSE BLOCK3. get_custom_mac Prints the Custom MAC Address. optional arguments: -h, --help show this help message and exit --chip {auto,esp32,esp32s2,esp32s3beta2,esp32s3beta3,esp32c3}, -c {auto,esp32,esp32s2,esp32s3beta2,esp32s3beta3,esp32c3} Target chip type --baud BAUD, -b BAUD Serial port baud rate used when flashing/reading --port PORT, -p PORT Serial port device --before {default_reset,no_reset,esp32r1,no_reset_no_sync} What to do before connecting to the chip --debug, -d Show debugging information (loglevel=DEBUG) --virt For host tests, the tool will work in the virtual mode (without connecting to a chip). --path-efuse-file PATH_EFUSE_FILE For host tests, saves efuse memory to file. --do-not-confirm Do not pause for confirmation before permanently writing efuses. Use with caution.
- espefuse.py 사용방법 (OTP 설정값 확인용)
실제 Serial에 연결하여 각 설정 값을 확인하도록 하자
(idf4.2_py3.8_env) D:\Works\Project> espefuse.py -p COM15 summary //OTP 설정된것 확인용 Connecting.... Detecting chip type... ESP32 espefuse.py v3.1-dev EFUSE_NAME (Block) Description = [Meaningful Value] [Readable/Writeable] (Hex Value) ---------------------------------------------------------------------------------------- Calibration fuses: BLK3_PART_RESERVE (BLOCK0): BLOCK3 partially served for ADC calibration data = False R/W (0b0) ADC_VREF (BLOCK0): Voltage reference calibration = 1114 R/W (0b00010) Config fuses: XPD_SDIO_FORCE (BLOCK0): Ignore MTDI pin (GPIO12) for VDD_SDIO on reset = False R/W (0b0) XPD_SDIO_REG (BLOCK0): If XPD_SDIO_FORCE, enable VDD_SDIO reg on reset = False R/W (0b0) XPD_SDIO_TIEH (BLOCK0): If XPD_SDIO_FORCE & XPD_SDIO_REG = 1.8V R/W (0b0) CLK8M_FREQ (BLOCK0): 8MHz clock freq override = 55 R/W (0x37) SPI_PAD_CONFIG_CLK (BLOCK0): Override SD_CLK pad (GPIO6/SPICLK) = 0 R/W (0b00000) SPI_PAD_CONFIG_Q (BLOCK0): Override SD_DATA_0 pad (GPIO7/SPIQ) = 0 R/W (0b00000) SPI_PAD_CONFIG_D (BLOCK0): Override SD_DATA_1 pad (GPIO8/SPID) = 0 R/W (0b00000) SPI_PAD_CONFIG_HD (BLOCK0): Override SD_DATA_2 pad (GPIO9/SPIHD) = 0 R/W (0b00000) SPI_PAD_CONFIG_CS0 (BLOCK0): Override SD_CMD pad (GPIO11/SPICS0) = 0 R/W (0b00000) DISABLE_SDIO_HOST (BLOCK0): Disable SDIO host = False R/W (0b0) Efuse fuses: WR_DIS (BLOCK0): Efuse write disable mask = 0 R/W (0x0000) RD_DIS (BLOCK0): Efuse read disable mask = 0 R/W (0x0) CODING_SCHEME (BLOCK0): Efuse variable block length scheme = NONE (BLK1-3 len=256 bits) R/W (0b00) KEY_STATUS (BLOCK0): Usage of efuse block 3 (reserved) = False R/W (0b0) Identity fuses: MAC (BLOCK0): Factory MAC Address = c4:dd:57:71:08:08 (CRC 0x5f OK) R/W MAC_CRC (BLOCK0): CRC8 for factory MAC address = 95 R/W (0x5f) CHIP_VER_REV1 (BLOCK0): Silicon Revision 1 = True R/W (0b1) CHIP_VER_REV2 (BLOCK0): Silicon Revision 2 = True R/W (0b1) CHIP_VERSION (BLOCK0): Reserved for future chip versions = 2 R/W (0b10) CHIP_PACKAGE (BLOCK0): Chip package identifier = 1 R/W (0b001) MAC_VERSION (BLOCK3): Version of the MAC field = 0 R/W (0x00) Security fuses: FLASH_CRYPT_CNT (BLOCK0): Flash encryption mode counter = 0 R/W (0b0000000) UART_DOWNLOAD_DIS (BLOCK0): Disable UART download mode (ESP32 rev3 only) = False R/W (0b0) FLASH_CRYPT_CONFIG (BLOCK0): Flash encryption config (key tweak bits) = 0 R/W (0x0) CONSOLE_DEBUG_DISABLE (BLOCK0): Disable ROM BASIC interpreter fallback = True R/W (0b1) ABS_DONE_0 (BLOCK0): Secure boot V1 is enabled for bootloader image = False R/W (0b0) ABS_DONE_1 (BLOCK0): Secure boot V2 is enabled for bootloader image = False R/W (0b0) JTAG_DISABLE (BLOCK0): Disable JTAG = False R/W (0b0) DISABLE_DL_ENCRYPT (BLOCK0): Disable flash encryption in UART bootloader = False R/W (0b0) DISABLE_DL_DECRYPT (BLOCK0): Disable flash decryption in UART bootloader = False R/W (0b0) DISABLE_DL_CACHE (BLOCK0): Disable flash cache in UART bootloader = False R/W (0b0) BLOCK1 (BLOCK1): Flash encryption key = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 R/W BLOCK2 (BLOCK2): Secure boot key = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 R/W BLOCK3 (BLOCK3): Variable Block 3 = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 R/W Flash voltage (VDD_SDIO) determined by GPIO12 on reset (High for 1.8V, Low/NC for 3.3V).
상위보다는 거의 사용하지 않겠지만, 다른 기계와 설정값 비교할때는 dump가 최고
(idf4.2_py3.8_env) D:\Works\Project> espefuse.py -p COM15 dump //OTP 설정된것 확인용
Connecting....
Detecting chip type... ESP32
BLOCK0 ( ) [0 ] read_regs: 00000000 57710808 005fc4dd 0000a200 00000237 00100000 00000004
BLOCK1 (flash_encryption) [1 ] read_regs: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
BLOCK2 (secure_boot_v1 s) [2 ] read_regs: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
BLOCK3 ( ) [3 ] read_regs: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
espefuse.py v3.1-dev
만약, MAC을 사서 사용한다면 그때 설정하고 확인
(idf4.2_py3.8_env) D:\Works\Project> espefuse.py -p COM15 get_custom_mac //기본으로 사용하므로 없음
Connecting.....
Detecting chip type... ESP32
espefuse.py v3.1-dev
Custom MAC Address is not set in the device.
- Secure Boot 설정완료 후 OTP 설정 할 것
SecureBoot를 완벽히 진행하려면, 나중에는 JTAG 과 UART를 막아야 하며, 이후 부터 OTA로만 진행
- ESP32 의 JTAG 막기
- ESP32 UART BOOT 막기
//EfuseDefineFields (idf4.2_py3.8_env) D:\Works\Project> espefuse.py -p COM15 burn_efuse JTAG_DISABLE 1 --before default_reset --do-not-confirm (idf4.2_py3.8_env) D:\Works\Project> espefuse.py -p COM15 burn_efuse UART_DOWNLOAD_DIS 1 --before default_reset --do-not-confirm //주의: 이후 Serial로 eFUSE도 동작안됨 (idf4.2_py3.8_env) D:\Works\Project> espefuse.py -p COM15 burn_efuse CONSOLE_DEBUG_DISABLE 1 (idf4.2_py3.8_env) D:\Works\Project> espefuse.py -p COM15 burn_bit BLOCK0 198 //DISABLE_JTAG , 상위와 동일 (idf4.2_py3.8_env) D:\Works\Project> espefuse.py -p COM15 burn_bit BLOCK0 27 //UART_DOWNLOAD_DIS (idf4.2_py3.8_env) D:\Works\Project> espefuse.py -p COM15 burn_bit BLOCK0 194 //CONSOLE_DEBUG_DISABLE
--before default_reset --do-not-confirm 반드시 사용
(idf4.2_py3.8_env) D:\Works\Project> espefuse.py -p COM15 adc_info (idf4.2_py3.8_env) D:\Works\Project> espefuse.py -p COM15 read_protect_efuse BLOCK0 (idf4.2_py3.8_env) D:\Works\Project> espefuse.py -p COM15 write_protect_efuse
eFUSE API를 사용할 경우