POSA_LEAKSMS/firmware
유창욱 90f121e14c chore: import codebase with security hardening
SHT30 온습도 모니터링 시스템 전체 소스(서버 PHP, STM32 펌웨어, SQL, 테스트).
전체 코드리뷰에서 도출된 보안 하드닝 10건 반영:
- 요청 서명 HMAC-SHA256 전환(펌웨어 sig.c/서버 config.php/호스트 패리티 동시)
- 재전송 방어 + 기본 API_KEY fail-closed + 디바이스 문자열 정제(api/sensor_data.php)
- 오프라인 SMS 중복 발송 경합 제거(cron_heartbeat.php, 원자적 선점)
- CSV 수식 주입 방지(monthly_report.php), 감사로그 회전 락(retention_cleanup.php)
- 브루트포스 카운터 원자화(login.php), 예시 TOTP 비밀키 무효화, 마이그레이션 멱등화

_backup/(하드코딩 실 비밀값 포함)·config.local.php·런타임 상태는 .gitignore 제외.
2026-06-20 09:37:40 +09:00
..
board_sht30 chore: import codebase with security hardening 2026-06-20 09:37:40 +09:00
certs chore: import codebase with security hardening 2026-06-20 09:37:40 +09:00
cmake chore: import codebase with security hardening 2026-06-20 09:37:40 +09:00
common chore: import codebase with security hardening 2026-06-20 09:37:40 +09:00
config chore: import codebase with security hardening 2026-06-20 09:37:40 +09:00
docs chore: import codebase with security hardening 2026-06-20 09:37:40 +09:00
ld chore: import codebase with security hardening 2026-06-20 09:37:40 +09:00
scripts chore: import codebase with security hardening 2026-06-20 09:37:40 +09:00
test/host chore: import codebase with security hardening 2026-06-20 09:37:40 +09:00
third_party chore: import codebase with security hardening 2026-06-20 09:37:40 +09:00
.gitignore chore: import codebase with security hardening 2026-06-20 09:37:40 +09:00
CMakeLists.txt chore: import codebase with security hardening 2026-06-20 09:37:40 +09:00
README.md chore: import codebase with security hardening 2026-06-20 09:37:40 +09:00
VERSION chore: import codebase with security hardening 2026-06-20 09:37:40 +09:00

SHT30 온습도 모니터링 STM32F407 펌웨어

Raspberry Pi SHT30 온습도 노드를 STM32F407VGT6 베어메탈 펌웨어로 대체한 프로젝트. 기존 서버측 PHP API / 서명 규약 / 요청 포맷을 바이트 단위로 그대로 재현하여 서버 무중단으로 RPi → STM32 전환을 목표로 한다.

  • 대상 MCU: STM32F407VGT6 (Cortex-M4F @168MHz, Flash 1MB, SRAM 192KB + CCM 64KB)
  • 네트워크: 유선 Ethernet (LAN8720 RMII PHY) → LwIP + mbedTLS (HTTPS)
  • 런타임: FreeRTOS (네이티브 API — task.h/queue.h/timers.h/semphr.h)
  • 빌드: arm-none-eabi-gcc 15.2 + CMake + Ninja
  • 배포: 폐쇄망(air-gapped) — 모든 서드파티 의존성은 third_party/에 벤더링, 빌드·런타임에 외부 다운로드 0건

상세 이식 근거는 ../docs/stm32f407_migration_plan.md 참고.


1. 시스템 아키텍처

물리 센서에서 SMS 발송까지의 전체 경로. 굵게 표시한 구간이 본 펌웨어가 대체하는 부분이다.

┌──────────────┐    I2C1     ┌────────────────────────────┐  Ethernet(RMII)  ┌──────────────┐
│  SHT30 센서   │ ──────────▶ │      STM32F407VGT6          │ ───────────────▶ │  LAN8720 PHY  │
│  (온습도)     │  PB6/PB7    │  (FreeRTOS 펌웨어)           │                  │  (외부 PHY)   │
│  0x44        │  (SCL/SDA)  │                            │                  └──────┬───────┘
│              │             │  Task_Net   : 링크/DHCP/SNTP │                         │ UTP
│              │             │  Task_Sensor: 5분 주기 측정   │                         ▼
│              │             │  Task_Report: TLS POST       │                  ┌──────────────┐
└──────────────┘             │  Task_Watchdog: IWDG refresh │                  │   스위치/공유기 │
                             └────────────────────────────┘                  └──────┬───────┘
                                                                                    │
                          lowercase_hex(sha256(API_KEY || raw_body))  X-Signature 헤더 │ HTTPS/TLS1.2
                                                                                    ▼
                                  ┌────────────────────────────────────────────────────────┐
                                  │            Cafe24 호스팅 (변경 없음)                       │
                                  │  sensor_data.php  ──▶  MySQL (로그 저장)                  │
                                  │       │                                                  │
                                  │       └──▶ 서버 임계 판정(config.php METRIC_*) ──▶ SMS    │
                                  └────────────────────────────────────────────────────────┘
  • 펌웨어는 매 보고마다 connect → TLS 핸드셰이크 → POST → close 를 수행한다(메모리 절약, 보고 주기가 길어 성능 무관).
  • 서명은 본문 바이트 그대로 sha256(API_KEY || body) 를 계산해 X-Signature 헤더로 보낸다. 본문의 키 정렬/float 포맷 재현이 불필요하다(서버 verify_signature_raw).
  • 임계(고온/저온/고습/저습) 판정과 SMS 발송은 서버(PHP)에서 수행한다. 펌웨어는 온습도 원값과 plausibility 상태(metric_status)만 보고하고, 경보 임계는 서버 config.phpMETRIC_* 상수가 결정한다(폐쇄망 재플래시 비용을 피하기 위함).

2. 보드 구성 (SHT30 단일 보드)

이 프로젝트는 SHT30 온습도 보드 단일 구성이다. CMake sht30_fw 타깃이 -DBOARD_SHT30 을 자동으로 주입한다.

빌드 매크로 CMake 타깃 산출물 sensor_id device_id 센서 동작
-DBOARD_SHT30 sht30_fw sht30_fw.bin 2 stm32-sht30-01 SHT30 온습도 주기(300s) I2C 측정 + 원값 보고

식별값 정의는 common/board_config.h(BOARD_SHT30 단일). 공통 튜너블은 common/app_config.h.


3. 기능 패리티 (RPi 대비)

기능 RPi (Python) STM32 펌웨어 상태
SHT30 측정/CRC smbus2 + crc8_sht3x HAL I2C + sht30_crc8/sht30_parse 유지
주기 측정/보고 300s (최소 30s) APP_SHT30_REPORT_INTERVAL_SEC (300s) 유지
metric_status 판정 classify_reading() plausibility reporter 호출부 metric_status 유지
임계 경보/SMS (서버측 처리) 서버 config.php METRIC_ 에서 판정* 유지(서버)
HTTP 재시도 requests + 루프(3회/5s) report_* 내부 재시도(3회/5s) 유지
TLS/HTTPS OpenSSL(requests) mbedTLS + LwIP 변경(이식)
네트워크 WiFi 유선 Ethernet(LAN8720 RMII) 변경(이식)
동시성 Python threads FreeRTOS tasks 변경(이식)
시간원 OS NTP SNTP(LwIP) → RTC 변경(이식)
서명 본문 필드 정규화 서명 raw-body X-Signature 변경(단순화)
프로세스 관리 systemd 재시작 IWDG 워치독 + 재연결 변경(이식)
로깅 로테이팅 파일 로그 UART 콘솔 변경(축소)

임계 판정의 위치: 펌웨어는 temperature_c/humidity_percent 원값과 metric_status(센서 물리/타당성 범위 기준 normal/out_of_range)만 보고한다. 고온/저온/고습/저습 경보와 SMS 트리거는 서버 config.phpMETRIC_* 임계가 담당한다. 폐쇄망에서 임계값을 조정할 때 펌웨어 재플래시가 불필요하도록 의도된 분리다.

자세한 함수 단위 매핑은 docs/PORTING_NOTES.md 참고.


4. 빠른 시작 (빌드/플래시)

폐쇄망 전체 절차는 docs/BUILD_OFFLINE.md. 핀맵/배선은 docs/HARDWARE.md.

# 0) 비밀값 준비 (한 번)
cp firmware/common/secrets.h.example firmware/common/secrets.h
#   secrets.h 의 APP_API_KEY 를 서버 php/config.php 의 API_KEY 와 동일하게 채운다.

# 1) 서버 루트 CA 임베드 (한 번)
#   실제 Cafe24 인증서 체인의 루트 CA PEM 으로 certs/server_ca.c 를 생성한다.
#   (certs/server_ca.h 가 SERVER_CA_PEM / SERVER_CA_PEM_LEN 을 extern 으로 선언함)

# 2) 빌드 (sht30_fw 단일 타깃)
cmake -S firmware -B firmware/build -G Ninja \
      -DCMAKE_TOOLCHAIN_FILE=firmware/cmake/arm-none-eabi.cmake
cmake --build firmware/build --target sht30_fw     # → sht30_fw.bin/.hex/.elf

# 3) 플래시 (ST-Link) — 보드 파라미터 없이 sht30_fw 고정
pwsh firmware/scripts/flash.ps1
#   대안: st-flash write firmware/build/sht30_fw.bin 0x08000000
#   대안: openocd -f interface/stlink.cfg -f target/stm32f4x.cfg \
#               -c "program sht30_fw.elf verify reset exit"

# 4) 콘솔 확인 (USART3 PD8/PD9, 115200 8N1)
#   부팅 로그 → 링크업/DHCP/SNTP → 첫 startup POST 200 확인

참고: cmake/, third_party/, board_sht30/, certs/server_ca.c 는 별도 작업으로 생성/벤더링된다(아래 디렉터리 맵 참고). 위 명령은 그 산출물이 갖춰진 뒤의 표준 절차다.


5. firmware/ 디렉터리 맵

firmware/
├─ README.md                  ← (이 문서)
├─ CMakeLists.txt             빌드 루트: sht30_fw 타깃, third_party 통합
├─ cmake/
│   └─ arm-none-eabi.cmake    크로스 툴체인 파일 (gcc 15.2)
├─ scripts/
│   ├─ vendor.*               빌드 머신에서 고정 버전 의존성 벤더링(폐쇄망 준비)
│   ├─ build.ps1              cmake configure + sht30_fw 빌드
│   └─ flash.ps1              sht30_fw 플래싱(보드 파라미터 없이 고정)
├─ common/                    공유 코드/계약 헤더
│   ├─ app_config.h           튜너블: API 호스트/포트/경로, 주기, 재시도, SNTP, 망주소, 워치독, 핀, SHT30 범위
│   ├─ board_config.h         BOARD_DEVICE_ID/LOCATION/SENSOR_ID/SENSOR_NAME (BOARD_SHT30 단일)
│   ├─ secrets.h.example      APP_API_KEY 템플릿 (실 secrets.h 는 운영자가 생성, 미커밋)
│   ├─ bsp.{h,c}              클럭/HAL/LED 초기화 (HSE 8MHz → 168MHz)
│   ├─ net.{h,c}              ETH MAC + LAN8720 RMII + LwIP, DHCP/static
│   ├─ timesync.{h,c}         SNTP → RTC
│   ├─ tls.{h,c}              mbedTLS over LwIP socket (transport_t 제공)
│   ├─ transport.h            바이트 스트림 전송 추상화(connect/send/recv/close)
│   ├─ httpapi.{h,c}          HTTP/1.1 POST 작성 + X-Signature + 상태 파싱  (검증된 코어)
│   ├─ sig.{h,c}              raw-body 서명 sig_raw_body()                  (검증된 코어)
│   ├─ jsonbody.{h,c}         본문 JSON 생성 jb_sht30_event                 (검증된 코어)
│   ├─ sha256_backend.h       SHA-256 추상 인터페이스
│   ├─ sha256_sw.c            자급식 SHA-256 (공개 도메인)                   (검증된 코어)
│   ├─ hexutil.{h,c}          바이트 ↔ 소문자 hex                           (검증된 코어)
│   ├─ sht30.h                SHT30 I2C 드라이버 계약
│   ├─ sht30_convert.{h,c}    SHT30 CRC8/변환 (이식성 순수 로직)             (검증된 코어)
│   ├─ reporter.h             이벤트 보고 헬퍼(본문→서명→POST→재시도) 계약
│   ├─ watchdog.h             IWDG 계약
│   └─ applog.h               UART 로그 계약
├─ board_sht30/               SHT30 보드(sensor_id=2) 진입점 + 드라이버
│   ├─ main.c                 FreeRTOS 태스크 구성 + 부팅
│   ├─ app_sht30.c            주기 측정 → metric_status → 보고
│   └─ sht30.c                HAL I2C 측정
├─ certs/
│   ├─ server_ca.h            SERVER_CA_PEM / SERVER_CA_PEM_LEN extern 선언
│   └─ server_ca.c            실제 Cafe24 루트 CA PEM (운영자가 교체)  ← TODO(hw)
├─ third_party/               벤더링(폐쇄망): CMSIS, STM32F4_HAL, FreeRTOS-Kernel, lwip, mbedtls
├─ test/host/                 호스트 빌드 패리티 테스트 (PHP 기대값 대조)
│   ├─ parity_test.py
│   ├─ php_verify.php
│   └─ reference.py
├─ build/                     CMake/Ninja 산출물 (armgate: 호스트 컴파일 게이트 .o)
└─ docs/
    ├─ HARDWARE.md            SHT30 보드 핀맵/배선/핀 충돌 해소
    ├─ BUILD_OFFLINE.md       폐쇄망 빌드·전달·플래시·롤백 절차
    └─ PORTING_NOTES.md       Python → C 매핑 / 동작 패리티 / 변경점 / TODO(hw)

위 맵 중 CMakeLists.txt, cmake/, scripts/, board_sht30/, third_party/, certs/server_ca.c 는 별도 작업으로 채워진다. 본 문서·common/·certs/server_ca.h·test/·build/ 산출물은 이미 존재한다.


6. 핵심 제약 (반드시 인지)

  • 하드웨어 해시 가속기 없음: STM32F407 에는 CRYP/HASH 블록이 없다(F415/F417 전용). SHA-256/AES 는 소프트웨어로 수행한다. RSA-2048 핸드셰이크는 ~12s 소요(보고 주기가 길어 허용).
  • 하드웨어 RNG 있음: TLS 엔트로피 소스로 사용한다.
  • SRAM 예산이 최대 리스크: mbedTLS record buffer 를 MBEDTLS_SSL_IN/OUT_CONTENT_LEN ≈ 4096 으로 축소하고, FreeRTOS 태스크 스택을 CCM(64KB)에 배치한다(linker script).
  • 폐쇄망: 빌드·설치·런타임 모두 인터넷 접근 불가를 가정한다. 모든 의존성·인증서·SNTP 대상 IP 까지 사전에 준비한다(자세히 docs/BUILD_OFFLINE.md).