YOGYUI

[ESP32] Secure Cert 영역의 Pre-Provisioned 바이너리 추출해보기 본문

Hardware/Espressif

[ESP32] Secure Cert 영역의 Pre-Provisioned 바이너리 추출해보기

요겨 2024. 2. 2. 00:51
반응형

Extract pre-provisioned binary from secure_cert partition of ESP32

ESP32를 이용한 Matter 제품 개발 시, 회사(혹은 개인) 차원의 PKI(Public Key Infrastructure, 공개 키 인프라)를 구축하지 않았다면 ESP32의 파티션 테이블 중 일부 영역에 PAI(Product Attestation Authority)DAC(Device Attestation Certificates) 인증서를 ESP32 출고 단계에서 포함된 상태로 주문할 수 있다

  • EspressIf는 CSA(Connectivity Standards Alliance)의 멤버
  • Digicert, Amazon AWS 등 상용 PKI의 PAA 도입하려면 수백만원 예산이 추가로 필요하다

https://docs.espressif.com/projects/esp-matter/en/latest/esp32/production.html#manufacturing-partition

 

 

EspressIf에 문의하면 아래와 같은 형태의 신청서 양식을 보내주는데, 여기에 CSA로부터 발급받은 Vendor ID와 개발한 제품의 Product ID를 기입하여 보내면 된다

 

Pre-provisioned ESP32 신청 시 체크포인트는 다음과 같다

  • Secure Boot가 활성화되어야 하며, 암호화된 부트로더의 파일 이름과 MD5 체크섬값을 보내야 한다
  • Flash Encryption이 활성화되어야 한다
  • esp_secure_cert (type 0x3F) 파티션이 존재해야 하며, 파티션 테이블의 오프셋(Offset)값을 보내야 한다

제품 양산용 sdkconfig는 다음과 같이 설정해주는게 일반적이다

(부트로더, 플래시 암호화 + UART, JTAG를 통한 펌웨어 접근 원천 차단)

CONFIG_SECURE_BOOT=y
CONFIG_SECURE_BOOT_V2_ENABLED=y
CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES=y
CONFIG_SECURE_DISABLE_ROM_DL_MODE=y
CONFIG_SECURE_BOOT_INSECURE=y
CONFIG_SECURE_BOOT_ALLOW_JTAG=n
CONFIG_SECURE_FLASH_UART_BOOTLOADER_ALLOW_ENC=n
CONFIG_SECURE_FLASH_UART_BOOTLOADER_ALLOW_DEC=n
CONFIG_SECURE_FLASH_ENC_ENABLED=y
CONFIG_SECURE_FLASH_ENCRYPTION_MODE_RELEASE=y

 

EspressIf에서 Matter pre-provisioned ESP32를 구매하고 사용하는 방법에 대해서는 다른 글에서 더 자세히 다뤄보기로 한다 ^^;; (본 글의 주제와 벗어날 뿐더러 쓸 내용이 꽤 많다 ㅋㅋ)

 

이 글에서는 secure_cert 영역에 올라가있는 바이너리 파일을 로그로 읽어 텍스트 파일로 저장한 뒤, 이를 바이너리 파일로 저장하는 방법에 대해 알아보며, 다음 글에서는 해당 바이너리 파일을 파싱하여 내부에 들어있는 PAI, DAC 인증서를 확인해보기로 한다

※ 개인의 호기심 차원에서 진행한 내용이라 크게 도움될 내용은 없음 ㅎㅎ...

1. 개요

ESP32의 Flash Encryption이 활성화되면, ESP32의 eFUSE에 존재하는 암호화 키(encryption key)가 없을 경우 외부에서 플래시 내부 내용을 읽어봐야 전혀 해독할 수 없게된다 (물론 eFUSE의 암호화 키를 외부에서 추출하는 것은 불가능에 가깝다)

 

결국 pre-provisioned된 영역은 펌웨어 내부에서만 접근하여 읽을 수 있는데, ESP32에서 파일을 송/수신하려면 별도의 코딩 작업이 필요하기 때문에 이 글에서는 간단하게 바이너리 파일 내용을 복호화(decryption)한 뒤 헥사 포맷 텍스트로 로그로 뿌린 뒤 이를 텍스트 파일로 저장하는 방식을 소개한다

 

※ 파티션 테이블의 esp_secure_cert는 esp-matter의 예제들에서 8192바이트를 사용하길래 나도 동일하게 사용했다

# Name,   Type, SubType, Offset,  Size, Flags
# Note: Firmware partition offset needs to be 64K aligned, initial 36K (9 sectors) are reserved for bootloader and partition table
esp_secure_cert,  0x3F, ,0xd000,    0x2000, encrypted
nvs,      data, nvs,     0x10000,   0xC000,
nvs_keys, data, nvs_keys,,          0x1000, encrypted
otadata,  data, ota,     ,          0x2000
phy_init, data, phy,     ,          0x1000,
ota_0,    app,  ota_0,   0x20000,   0x1E0000,
ota_1,    app,  ota_1,   0x200000,  0x1E0000,
fctry,    data, nvs,     0x3E0000,  0x6000

2. Secure Cert 파티션 영역 전부 읽어서 로그로 출력 후 텍스트 파일로 저장

암호화된 플래시 내부 영역을 복호화해서 읽으려면 ESP-IDF의 <esp_flash.h>에 선언되어 있는 esp_flash_read_encrypted 함수를 사용하면 된다

https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/spi_flash/index.html

 

또한, 복호화하고자하는 영역의 플래시 주소를 알기 위해서는 <esp_partition.h>에 선언되어 있는 esp_partition_find esp_partition_get 함수를 사용하면 된다

https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/partition.html#_CPPv418esp_partition_find20esp_partition_type_t23esp_partition_subtype_tPKc

 

 

API 문서를 참고해 아래와 같이 간단하게 함수를 만들어주자

#include <esp_flash.h>
#include <esp_partition.h>
#include <esp_log.h>
#include <string>
#include <cstring>

#define PARTITION_NAME "esp_secure_cert"
#define PARTITION_SIZE 0x2000

void read_secure_cert_partition()
{
    esp_partition_iterator_t esp_part_it = esp_partition_find(
        0x3F, ESP_PARTITION_SUBTYPE_ANY, PARTITION_NAME);
    const esp_partition_t *partition = esp_partition_get(esp_part_it);
    uint32_t address = partition->address;
    uint32_t read_size = MIN(partition->size, PARTITION_SIZE);
    std::string str_contents;
    char *buffer = new char[read_size];
    if (esp_flash_read_encrypted(nullptr, address, (void*)buffer, read_size) == ESP_OK) {
        char temp[16]{};
        for (int r = 0; r < read_size / 16; r++) {
            str_contents = "";
            for (int c = 0; c < 16; c++) {
                snprintf(temp, 16, "%02X ", buffer[r * 16 + (15 - c)]);
                str_contents += temp;
            }
            snprintf(temp, 16, "[%4d:%4d]", (r + 1) * 16 - 1, r * 16);
            str_contents += temp;
            ESP_LOGI("logger", "%s", str_contents.c_str());
        }
    }    
    delete[] buffer;
}

 

파티션 테이블에 설정해둔 esp_secure_cert라는 이름의 파티션 (파티션 타입 = 0x3F)으로부터 최대 8192바이트 (0x2000)를 읽은 뒤 해당 내용을 헥사 포맷 스트링으로 1바이트씩 끊어서 로그로 출력하게 구현했다

ESP32 펌웨어에 올린 뒤 위에서 구현한 함수 read_secure_cert_partition() 을 호출한 뒤 시리얼(UART0)로 모니터링(idf.py monitor 명령어 사용)해보면 다음과 같은 결과를 얻을 수 있다

 

중요하게 확인해봐야 할 건 최초 4바이트 (array[0:4])

TLV 매직바이트 0xBA5EBA11 가 최초 4바이트와 일치해야만 esp_secure_cert 파티션 내에 정상적인 TLV 포맷의 인증서가 pre-provisioned되었다고 ESP가 인식할 수 있다

※ Secure Cert 영역에서 DAC를 불러오는 시퀀스가 궁금한 개발자는 connectedhomeip/src/platform/ESP32/ESP32SecureCertDACProvider.cpp 코드의 GetDeviceAttestationCert 메서드를 참고하면 된다 (코드를 타고 따라가다보면 esp_secure_cert_tlv_private.h 헤더파일에서 위 사진의 snippet을 마주칠 수 있다

 

이제 터미널 창의 로그를 복사-붙여넣기 해서 텍스트 파일(esp_secure_cert_raw_binary.txt) 파일로 저장해주자

3. 바이너리 파일로 변환

앞서 저장한 텍스트 파일을 바이너리(.bin) 파일로 변환하는 파이썬 코드를 아래와 같이 구현해봤다

(단순하게 구조화된 문자열을 파싱하는게 전부이기 때문에 별도로 코드 설명은 하지 않는다)

import os
cur_path = os.path.dirname(os.path.abspath(__file__))
bin_raw_text = os.path.join(cur_path, 'esp_secure_cert_raw_binary.txt')

with open(bin_raw_text, 'r') as fp:
    lines = fp.readlines()

lines = [x.split(':')[1] for x in lines]
lines = [x.split('[')[0].strip() for x in lines]

def convert_to_bytes_array(line: str):
    values = [int(x, 16) for x in line.split(' ')]
    values = bytearray(values)
    return values[::-1]

result = bytearray()
for l in lines:
    result.extend(convert_to_bytes_array(l))

outfile = os.path.join(cur_path, 'esp_secure_cert.bin')
with open(outfile, 'wb') as fp:
    fp.write(result)

python으로 위 스크립트를 구동하면 동일 경로에 esp_secure_cert.bin 파일이 생성된다

당연한 소리이지만 파일 크기는 파티션 사이즈와 동일한 8192바이트 ㅋㅋ

4. TODO

추출한 바이너리 파일 내부에는 TLV(Type, Length, Value) 포맷의 데이터가 존재하며, PAI/DAC 두 종류의 DER(Distinguished Encoding Rules) 양식의 (X.509 인코딩된) 인증서가 존재한다

다음 글에서는 Python으로 간단하게 X.509 복호화를 통해 pre-provisioned된 PAI/DAC 인증서를 해독해 읽어보도록 한다

 

반응형