본문으로 건너뛰기
Bootloader Internals · 27/37

임베디드 Chain of Trust — 다단계 서명 검증의 전체 흐름

· Hawk · 11분 읽기

Ch 16에서 U-Boot의 FIT verified boot 한 단계를 봤습니다. 이 장에서는 시야를 넓혀 전체 체인을 끝에서 끝까지 따라갑니다. eFuse에 박힌 PK hash에서 시작해 BootROM, BL1·BL2·BL31, U-Boot Proper, 커널, 그리고 커널 모듈까지. 각 단계가 어떤 키로 무엇을 검증하는지, 한 단계라도 깨지면 어디서 어떻게 거부되는지 봅니다.

#한 줄 요약

신뢰 체인은 변경 불가능한 eFuse PK hash에서 출발해 BootROM → BL1 → BL2 → BL31 → BL33 → 커널 → 모듈로 수직 인계됩니다. 각 단계가 다음 단계의 서명을 검증하고, 한 번이라도 매치가 깨지면 부팅이 끊깁니다.

#Root of Trust — eFuse부터 시작

신뢰 체인은 변경 불가능한 한 곳에서 출발해야 합니다. 그렇지 않으면 공격자가 그 시작점을 바꿔 체인 전체를 뒤집을 수 있습니다. SoC 입장에서 변경 불가능한 자리는 두 가지뿐입니다. 하나는 mask ROM(BootROM 코드 자체), 다른 하나는 eFuse(한 번 굽고 나면 못 되돌리는 비트). 키를 박는 자리는 eFuse입니다. mask ROM은 SoC 출고 시 굳어지지만, 키는 제품마다 달라야 하기 때문입니다.

eFuse에 키 자체를 넣지는 않습니다. 키는 보통 2048비트 RSA public key라 수백 바이트인데, eFuse는 비싸서 256비트가 한계입니다. 대신 public key의 SHA-256 hash를 넣습니다. BootROM은 다음 단계 이미지에 함께 실려 오는 public key를 읽고, 그 SHA-256을 계산해 eFuse의 hash와 비교합니다. 일치하면 그 키로 이미지의 서명을 검증합니다.

hash 비교 한 줄이 체인의 입니다. 공격자가 자신의 키로 이미지에 서명하더라도, 그 키의 hash가 eFuse 값과 다르므로 BootROM이 거부합니다. eFuse는 한 번 굽혀 있어 바꿀 수 없으니 닻은 빠지지 않습니다.

eFuse에 박는 이 hash를 NXP는 SRK hash(Super Root Key), Rockchip은 PK hash, TI는 ROTPK hash(Root Of Trust Public Key)라 부릅니다. 이름은 다르지만 역할은 같습니다. Ch 23에서 eFuse의 물리적 구조를 다뤘다면, 이 장은 그 위에 가 어떻게 얹히는지 봅니다.

#단계별 검증 흐름

전체 체인을 수직 인계 구조로 펼치면 다음과 같습니다.

Chain of Trust — eFuse PK hash(RoT) → BootROM이 SPL 검증 → BL1→BL2→BL31/32/33 단계별 RSA 서명 검증 → U-Boot이 FIT 검증 → 커널이 모듈 서명 검증 → user space는 dm-verity로 rootfs 무결성

각 단계가 다음 단계의 서명자기 키로 검증합니다. 한 단계라도 키가 없거나, 서명이 깨졌거나, anti-rollback 카운터가 옛 버전이면 부팅이 즉시 끊깁니다.

단계별로 어떤 키가 어떤 자리에 있는지 표로 정리합니다.

단계검증 주체검증 대상키 위치키 종류
0BootROMBL1/SPLeFuse (hash) + 이미지 묶음 (full key)PK / SRK / ROTPK
1BL1BL2BL1 안에 임베드 또는 FIP 인증서trusted boot firmware key
2BL2BL31 / BL32 / BL33FIP의 X.509 cert chaintrusted key (intermediate)
3U-Boot (BL33)kernel + DT + initramfs (FIT)U-Boot control DT의 /signature 노드FIT signing key
4kernelkernel module (.ko).builtin_trusted_keys 키링 (vmlinux 임베드)MODULE_SIG_KEY
5user spacerootfs blocksDT 또는 FIT에 박힌 root-hashdm-verity

#키와 인증서의 계층

각 단계가 키 하나를 들고 있지 않습니다. X.509 cert chain으로 키 계층을 만듭니다. ROM이 root key 하나만 알고, root key가 중간 키를 인증하고, 중간 키가 이미지별 키를 인증하는 구조입니다. 이렇게 두면 이미지별 키 하나가 노출돼도 root key는 안 다칩니다.

ARM TF-A의 Trusted Boot Boot Sequence 표준은 다음 인증서 계층을 정의합니다.

[ROTPK] ← eFuse hash로 닻
├── trusted-boot-fw-cert.crt (BL2 서명)
│ └── BL2 이미지 hash
├── trusted-key-cert.crt (중간 키)
│ │
│ ├── soc-fw-key-cert.crt → soc-fw-content-cert.crt (BL31 hash)
│ ├── tos-fw-key-cert.crt → tos-fw-content-cert.crt (BL32 hash)
│ └── nt-fw-key-cert.crt → nt-fw-content-cert.crt (BL33 hash)

ROTPK이 trusted-boot-fw-certtrusted-key-cert 둘을 직접 서명합니다. 그 아래로 soc-fw / tos-fw / nt-fw 세 갈래가 갈라지고, 각 갈래가 key cert + content cert 두 단을 가집니다. content cert가 이미지의 SHA-256 hash를 담고, key cert가 그 content cert에 서명한 키의 정보를 담습니다.

이 분할은 키 회전 비용을 줄입니다. BL33만 갱신했다면 nt-fw-content-cert만 다시 서명하면 됩니다. ROTPK은 절대 안 건드립니다.

#HABv4 — NXP i.MX 패턴

NXP HABv4(High Assurance Boot v4)는 i.MX 6 / 7 / 8M에서 쓰이는 BootROM 검증 시스템입니다. 키 계층은 SRK(Super Root Key) 1 ~ 4개, CSF Key, Image Key의 3단입니다. SRK 4개의 hash가 eFuse에 박히고, 활성 SRK가 CSF Key를 인증하고, CSF Key가 Image Key를 인증하고, Image Key가 실제 이미지의 hash를 서명합니다. SRK 4개를 둔 이유는 키 회전입니다. SRK1이 노출되면 SRK1만 revoke하고 SRK2로 옮길 수 있습니다.

서명 흐름은 NXP의 Code Signing Tool(cst)이 묶어 줍니다. 입력은 CSF input file이고 출력은 CSF binary입니다. CSF binary는 IVT(Image Vector Table)를 통해 이미지 뒤에 붙습니다.

Terminal window
# CSF input 작성 후 cst로 서명
cst -i csf_uboot.txt -o csf_uboot.bin
# 서명된 CSF binary를 U-Boot 이미지 뒤에 붙이기
cat u-boot-dtb.imx csf_uboot.bin > u-boot-signed.imx

csf_uboot.txt서명할 영역어떤 키를 쓸지를 기술합니다. 핵심은 Authenticate DataBlocks 한 줄입니다. 시작 주소 + 오프셋 + 크기 + 파일명 4개로 “이 범위를 이 키로 서명한다”를 기술합니다.

[Header]
Version = 4.3
Hash Algorithm = sha256
Engine = CAAM
[Install SRK]
File = "../crts/SRK_1_2_3_4_table.bin"
Source index = 0
[Install CSFK]
File = "../crts/CSF1_1_sha256_4096_65537_v3_usr_crt.pem"
[Authenticate CSF]
[Install Key]
Verification index = 0
Target index = 2
File = "../crts/IMG1_1_sha256_4096_65537_v3_usr_crt.pem"
[Authenticate Data]
Verification index = 2
Blocks = 0x877FF400 0x00000000 0x0009E000 "u-boot-dtb.imx"

IVT 구조는 C struct로 보면 한눈에 들어옵니다.

struct ivt {
uint32_t header; // 0x402000D1 (tag, length, version)
uint32_t entry; // U-Boot 진입 주소
uint32_t reserved1;
uint32_t dcd_ptr; // Device Configuration Data 위치
uint32_t boot_data_ptr; // boot_data struct 위치
uint32_t self; // IVT 자신의 절대 주소
uint32_t csf; // CSF binary 위치 (서명 데이터)
uint32_t reserved2;
};

BootROM은 *header tag(0x402000D1)*를 보고 IVT를 인식한 뒤, csf 필드를 따라가 CSF binary를 읽어 서명을 검증합니다. SRK fuse를 굽고 HAB_TYPEclosed로 설정하면 그때부터 서명되지 않은 이미지는 BootROM이 거부합니다. fuse를 굽기 전에는 open 모드라 안 잠긴 상태로 동작합니다.

#Rockchip secure boot

Rockchip은 loader1(MiniLoader / TPL+SPL)과 loader2(U-Boot Proper)를 RSA-2048 키 한 쌍으로 서명합니다. 도구는 rkdeveloptoolrk_sign_tool입니다.

Terminal window
# RSA 키 쌍 생성
openssl genrsa -out rk_priv.pem 2048
openssl rsa -in rk_priv.pem -pubout -out rk_pub.pem
# loader1 서명
rk_sign_tool sign --key rk_priv.pem \
--image rk356x_spl_loader.bin \
--output rk356x_spl_loader_signed.bin
# eFuse에 public key hash 굽기 (irreversible)
rkdeveloptool wf 0x10 rk_pub_hash.bin
rkdeveloptool wf 0x20 efuse_secure_enable.bin

마지막 두 줄이 완전 비가역입니다. 한 번 wf(write fuse)로 secure enable까지 켜면 그 보드는 영원히 이 키 외에 안 받습니다. 양산 라인에서 키 백업이 안 된 상태로 이걸 굽는 사고가 가끔 일어납니다. 그 보드는 폐기입니다. Rockchip은 FIT 호환이라 loader2 이후는 U-Boot 표준 verified boot 흐름과 같습니다.

#TI K3 SECDEV

TI K3 (AM62 / AM64 / J7)는 SECDEV 빌드 시스템과 SYSFW signing을 씁니다. 키 계층이 4단으로 가장 복잡합니다.

용도보관
ROTPKRoot, eFuse hashHSM
BMPKBoot Manager (보드별)HSM
SMPKSecondary Manufacturing (벤더)양산 라인 HSM
INTPKIntermediate (이미지별)빌드 서버

ROTPK가 BMPK·SMPK 인증서에 서명하고, BMPK/SMPK가 INTPK 인증서에 서명하고, INTPK가 실제 이미지(SYSFW, tiboot3, tispl, u-boot)에 서명합니다. 빌드는 tisdk-secure 환경에서 다음 흐름으로 진행됩니다.

Terminal window
# K3 SECDEV 환경에서 이미지 서명
export TI_SECURE_DEV_PKG=/path/to/k3-image-gen
make -C ${TI_SECURE_DEV_PKG} K3_HSFS_KEY=int.pem \
K3_HSFS_TYPE=signed \
SOC=am64x \
HS=1 \
tiboot3.bin
# 결과: tiboot3-am64x-hs-evm.bin (signed)

SYSFW는 Cortex-M3 Device Management Security Controller 위에서 동작하는 시스템 펌웨어인데, 이것 자체도 서명되어야 합니다. SYSFW가 부트 chain의 첫 단계에 합세하기 때문에 SYSFW 서명을 빠뜨리면 그 위의 모든 단계가 secure 부팅 모드에서 거부됩니다.

K3는 HSM 강제도 가장 엄격합니다. ROTPK·BMPK는 반드시 HSM에서 동작하고, 빌드 서버는 INTPK 키만 접근할 수 있게 권한을 설계합니다.

#U-Boot FIT — verified boot

U-Boot 단계는 Ch 16에서 자세히 다뤘으니 핵심만 요약합니다. mkimageFIT 서명control DT에 public key embed를 한 번에 수행합니다.

Terminal window
mkimage -f boot.its \
-k keys \
-K u-boot.dtb \
-r \
-G keys/dev.key \
boot.itb

.its 파일의 signature 노드는 configuration 단위입니다. 이미지 단위가 아닙니다.

configurations {
default = "conf-1";
conf-1 {
description = "boardX boot";
kernel = "kernel-1";
fdt = "fdt-1";
ramdisk = "ramdisk-1";
hash-1 { algo = "sha256"; };
signature-1 {
algo = "sha256,rsa2048";
key-name-hint = "dev";
sign-images = "kernel", "fdt", "ramdisk";
required = "conf";
};
};
};

required = "conf"체인의 마지막 못입니다. 이 표시가 없으면 U-Boot이 서명 안 된 FIT도 허용해 체인이 풀립니다. 부팅 로그에서 + OK가 보이면 검증 성공, - Bad 또는 error!가 보이면 거부입니다.

#kernel module signing

체인은 user space까지 가야 닫힙니다. 커널을 검증해도 런타임에 임의의 모듈insmod로 불러올 수 있다면 root 권한을 얻은 공격자가 커널 메모리를 자유롭게 만질 수 있습니다. 모듈 서명 강제가 그 구멍을 막습니다.

CONFIG_MODULE_SIG=y
CONFIG_MODULE_SIG_FORCE=y
CONFIG_MODULE_SIG_ALL=y
CONFIG_MODULE_SIG_SHA256=y
CONFIG_MODULE_SIG_KEY="certs/signing_key.pem"

CONFIG_MODULE_SIG_FORCE=y런타임 강제입니다. 서명 안 된 모듈은 insmod-EKEYREJECTED로 실패합니다. CONFIG_MODULE_SIG_KEY는 빌드 시 자동으로 모듈에 서명할 키 경로입니다. 빌드가 끝나면 vmlinux.builtin_trusted_keys 키링에 해당 키의 public 부분이 박힙니다. out-of-tree 모듈이나 vendor 드라이버는 scripts/sign-file로 수동 서명합니다.

Terminal window
scripts/sign-file sha256 \
certs/signing_key.pem \
certs/signing_key.x509 \
drivers/my_module.ko

서명이 끝나면 .ko 파일 끝에 PKCS#7 서명 블록과 *magic string ~Module signature appended~*이 추가됩니다. CONFIG_LOCKDOWN_LSM까지 켜면 서명 없이는 /dev/mem·kexec·BPF kprobe도 막혀 체인의 마지막 한 줄이 닫힙니다.

#키 rollover와 revocation

키는 언젠가 노출됩니다. 양산 라인 도구가 털리거나, 빌드 서버가 침해되거나, 퇴사한 엔지니어가 USB를 들고 나가거나. 키가 언제 노출돼도 그 키를 무효화하고 새 키로 옮길 수 있어야 합니다. 두 가지 매커니즘이 있습니다.

첫째, eFuse의 키 revocation 비트입니다. NXP HABv4는 SRK 4개와 revocation fuse 4비트를 따로 둡니다. SRK2가 노출됐다면 그 비트를 굽고 SRK3으로 옮깁니다. ROTPK은 손대지 않고 운영 키만 회전합니다.

둘째, anti-rollback counter입니다. 노출된 키로 서명된 옛 펌웨어를 공격자가 다시 flash해서 과거의 취약점을 재활용하는 공격을 막습니다. eFuse에 monotonic counter를 두고 펌웨어마다 최소 허용 버전을 박은 뒤, 부팅 시 eFuse 카운터 ≥ 펌웨어 버전인지 확인합니다.

[펌웨어 v1] → eFuse counter = 1
[펌웨어 v2] → eFuse counter = 2 ← 한 번 굽힘
[펌웨어 v1] flash 시도 → counter 1 < 2 → BootROM이 거부

OTP 갱신은 부팅 성공이 며칠 검증된 뒤 적용하는 것이 안전합니다. 새 펌웨어가 부팅하자마자 카운터를 올렸다가 그 펌웨어에 큰 버그가 있어 롤백해야 한다면, 이미 카운터가 올라가서 영영 되돌릴 수 없습니다.

#단계별 failure 진단

서명 체인이 깨지면 어디서 깨졌는지가 중요합니다. 단계마다 고유한 에러 메시지가 떨어집니다.

단계실패 시 메시지원인
BootROM(보통 침묵, USB recovery 모드 진입)eFuse hash와 이미지 키 불일치, 또는 서명 없음
BootROM (i.MX)HAB Event: 0xdb, 0x00, ...HAB가 이미지 거부, hab_status 명령으로 분석
BL1 → BL2BL1: Failed to authenticate BL2FIP 안 trusted-boot-fw-cert 서명 깨짐
BL2 → BL31Authentication failure for image id 6content cert hash mismatch
U-Boot FITBad Data Hash 또는 Verifying Hash Integrity ... error!FIT image hash mismatch
U-Boot FITNo signature found in image서명이 아예 없음, -r 빠뜨림
kernel moduleinsmod: ERROR: could not insert module ...: Required key not available.ko에 서명 없음 또는 trusted_keys에 키 없음
kernel moduleModule verification failed: signature and/or required key missingCONFIG_MODULE_SIG_FORCE=y 강제 모드

U-Boot FIT 단계의 전형적인 거부 로그입니다.

=> bootm 0x40000000
## Loading kernel from FIT Image at 40000000 ...
Using 'conf-1' configuration
Verifying Hash Integrity ... sha256,rsa2048:dev error!
Bad Data Hash
ERROR: can't get kernel image!

sha256,rsa2048:dev error! 한 줄이 결정적입니다. 서명 알고리즘은 인식했고 키 이름도 찾았는데, hash가 안 맞는다는 뜻입니다. mkimage 단계에서 서명 후 이미지를 또 건드린 경우(예: 다른 mkimage로 wrap을 한 번 더 함)가 가장 흔한 원인입니다.

i.MX HAB 거부는 침묵 또는 recovery 모드 진입이라 더 까다롭습니다. U-Boot 콘솔까지 도달했다면 hab_statusHAB event log를 덤프해 분석합니다.

=> hab_status
Secure boot enabled
HAB Configuration: 0xcc, HAB State: 0x99
--------- HAB Event 1 -----------------
STS = HAB_FAILURE (0x33)
RSN = HAB_INV_SIGNATURE (0x18)
CTX = HAB_CTX_COMMAND (0xc0)

HAB_INV_SIGNATURE가 결정적입니다. 이미지의 서명이 그 보드의 SRK와 짝이 안 맞습니다. 양산 키와 개발 키를 헷갈려서 굽거나, SRK fuse를 굽기 전에 open 모드에서 빌드한 이미지를 closed 보드에 올리면 이 메시지가 떨어집니다.

#정리

  • 신뢰 체인의 시작점은 변경 불가능한 eFuse PK hash입니다. 키 자체가 아니라 SHA-256 hash만 박습니다.
  • BootROM이 BL1을 검증하고, BL1이 BL2를, BL2가 BL31·BL32·BL33을, BL33(U-Boot)이 FIT을, 커널이 모듈을, dm-verity가 rootfs를 검증합니다. 수직 인계 구조입니다.
  • X.509 cert chain으로 root key는 손대지 않고 중간 키 / 이미지 키를 회전할 수 있습니다. ROTPK은 영원, 나머지는 노출되면 교체합니다.
  • NXP HABv4는 SRK 4개와 CSF / Image Key 3단을, Rockchip은 단일 RSA-2048을, TI K3는 ROTPK·BMPK·SMPK·INTPK 4단을 씁니다. 키 계층 깊이와 HSM 강제 수준이 다릅니다.
  • U-Boot FIT은 configuration 단위로 서명해야 mix-and-match 공격을 막을 수 있습니다. required = "conf"가 강제 표시입니다.
  • 커널 모듈도 체인에 포함됩니다. CONFIG_MODULE_SIG_FORCE=y가 없으면 user space에서 임의 모듈로 체인을 깰 수 있습니다.
  • anti-rollback counter가 유효한 서명이 박힌 옛 펌웨어로의 롤백 공격을 막습니다. 카운터 갱신은 부팅 검증 후 신중하게 합니다.
  • 단계별 실패 메시지가 다르므로 어디서 깨졌는지 구분할 수 있습니다. HAB_INV_SIGNATURE, Bad Data Hash, Required key not available이 대표적입니다.

#다음 장 예고

체인이 닫혀도 어디에 무엇을 둘지가 정해지지 않으면 부팅 자체가 안 됩니다. 다음 장에서는 SPL·U-Boot·FIT·환경 변수·rootfs·A/B 슬롯을 eMMC / SPI flash에 어떻게 배치할지, partition table과 offset 설계 패턴을 봅니다.

#관련 항목

Bootloader Internals · 27 of 37

  1. 1ROM부터 init까지 — 임베디드 부팅 단계의 빈자리 분석
  2. 2Das U-Boot vs TF-A vs EDK II — 임베디드 부트로더 생태계 비교
  3. 3U-Boot 빌드 시스템 분석 — Kconfig·Makefile·defconfig 동작 추적
  4. 4ARM 임베디드 부트 4단계 분해 — BL1·SPL·TPL·U-Boot Proper의 역할
  5. 5U-Boot Falcon Mode — SPL이 U-Boot Proper 없이 커널 직접 부팅
  6. 6Device Tree DTB 부트로더 처리 — 로딩 시점과 fixup 메커니즘 추적
  7. 7U-Boot Driver Model 내부 — uclass·driver·device 추상화 구조
  8. 8U-Boot 보드 초기화 시퀀스 — board_init_f와 board_init_r 분리 이유
  9. 9DDR Controller 프로그래밍과 PHY Training — SPL의 가장 어려운 작업
  10. 10임베디드 스토리지 부팅 분석 — MMC·SCSI·NAND·SPI Flash 비교
  11. 11임베디드 네트워크 부팅 — TFTP·PXE·BOOTP 시퀀스 분석
  12. 12U-Boot USB 부팅 — fastboot·UMS·USB host 메커니즘
  13. 13U-Boot 환경 변수와 bootcmd — 부팅 시나리오 정의하기
  14. 14Modern U-Boot bootflow / bootmeth — 새 추상화 레이어 분석
  15. 15FIT image 구조 분석 — multi-image·hash·configuration 추적
  16. 16U-Boot Verified Boot — RSA 서명과 public key 임베딩 흐름
  17. 17임베디드 A/B 부팅 이중화 — OTA 안전성을 위한 부트 슬롯 설계
  18. 18U-Boot의 EFI 호환 분석 — bootefi 명령과 EFI loader 동작 원리
  19. 19Linux Boot ABI — ARM/ARM64 커널 진입 규약 추적
  20. 20임베디드 펌웨어 업데이트 — RAUC vs SWUpdate 비교
  21. 21새 보드 U-Boot 포팅 실전 — defconfig 작성부터 첫 부팅까지
  22. 22부트로더 디버깅 기법 — DEBUG·JTAG·serial·post-mortem 분석
  23. 23SoC BootROM·eFuse·OTP — 부팅의 0단계 분석
  24. 24SPL·TPL 내부 해부 — 가장 작은 부트 단계의 동작 추적
  25. 25ARM Trusted Firmware-A 통합 — BL1·BL2·BL31·BL32·BL33 흐름
  26. 26DDR Training과 PHY Calibration — 보드별 파라미터 튜닝
  27. 27임베디드 Chain of Trust — 다단계 서명 검증의 전체 흐름
  28. 28임베디드 Flash Layout 설계 — partition·NAND·eMMC·UBI 비교
  29. 29U-Boot Distro Boot — extlinux·boot.scr 표준화 분석
  30. 30부트로더 CI 구축 — build matrix와 자동 부팅 테스트
  31. 31TF-A BL31 EL3 Runtime 분석 — PSCI·SDEI·RAS dispatcher 추적
  32. 32PSCI와 SMCCC ABI — ARM 표준 SMC 호출 규약 분석
  33. 33ARM64 Secondary Core Bring-up — PSCI CPU_ON 호출부터 EL1 진입까지
  34. 34U-Boot PCIe Enumeration — 부트로더가 디바이스를 찾는 흐름 분석
  35. 35EFI·UEFI에서 CXL 초기화 — CEDT 생성과 HDM Decoder 사전 설정
  36. 36부트 시 메모리 토폴로지 결정 — DDR + CXL.mem 통합 인식
  37. 37UEFI Secure Boot 인증서 만료 — 2011→2023 CA 롤오버와 PQC 대비