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 제외.
6.1 KiB
서버 루트 CA 인증서 (certs/)
STM32F407 펌웨어는 폐쇄망(air-gapped)에서 동작하므로 런타임에 CA 인증서를
다운로드할 수 없다. 따라서 서버(Cafe24) TLS 검증에 사용할 루트 CA PEM 을
flash 에 임베드한다(server_ca.c). mbedTLS(tls_mbedtls.c)가 부팅 시
mbedtls_x509_crt_parse() 로 이 PEM 을 파싱하고, 매 핸드셰이크마다 서버
인증서 체인을 검증한다.
| 파일 | 역할 |
|---|---|
server_ca.h |
SERVER_CA_PEM[] / SERVER_CA_PEM_LEN 선언 |
server_ca.c |
PEM 본문(현재 PLACEHOLDER, 반드시 교체) |
gen_ca_header.ps1 |
PEM 파일 → server_ca.c 재생성 스크립트 |
⚠️ 현재
server_ca.c는 자리표시자(PLACEHOLDER)다. 컴파일은 되지만 실제 서버 인증서 검증은 실패한다. 배포 전 반드시 실제 Cafe24 루트 CA 로 교체할 것.
1. 서버 인증서 체인 추출
배포 대상 호스트(app_config.h 의 APP_API_HOST, 기본 your-domain.example)에
대해 빌드 머신에서 다음을 실행한다. (-servername 으로 SNI 지정 필수 —
가상호스팅 환경에서 올바른 인증서를 받기 위함.)
HOST=your-domain.example # = APP_API_HOST 와 동일하게
# 전체 체인(leaf + intermediate + root) 보기
openssl s_client -showcerts -connect "$HOST:443" -servername "$HOST" </dev/null
출력의 -----BEGIN CERTIFICATE----- ~ -----END CERTIFICATE----- 블록들이 체인이다.
보통 순서는 [0] leaf(서버) → [1] intermediate → [2] root 다.
펌웨어 검증의 신뢰 앵커(trust anchor)는 root CA 다. 단, Cafe24 가 root 를 체인에 포함하지 않거나 cross-signed 인 경우가 있어, 확실한 검증을 위해 intermediate + root 를 한 PEM 파일에 연결해 두는 것을 권장한다(아래 2번).
2. 트러스트 PEM 만들기 (intermediate + root 연결)
가장 안전한 방법은 발급기관(CA)의 공식 root/intermediate PEM 을 받아 연결하는 것이다. 체인에서 직접 뽑으려면 아래처럼 블록을 분리/연결한다.
# s_client 출력에서 인증서 블록만 추려 파일로 저장(예: certs.pem)
openssl s_client -showcerts -connect "$HOST:443" -servername "$HOST" </dev/null \
| sed -n '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p' > chain_all.pem
# chain_all.pem 에서 intermediate + root 를 골라 cafe24_root.pem 으로 연결.
# (leaf[0] 는 제외. intermediate/root 만 trust 파일에 둔다.)
# - 가장 확실: CA 벤더 공식 사이트에서 root/intermediate PEM 다운로드 후 cat 으로 연결
cat intermediate.pem root.pem > cafe24_root.pem
팁: root 만으로 검증이 되면 root 한 장이면 충분하다. intermediate 까지 넣으면 서버가 intermediate 를 안 보내는 경우에도 검증이 안정적이다(여분은 무해).
3. 체인 / cipher 사전 검증 (위험 R3 사전조사)
펌웨어에 넣기 전에, 추출한 trust PEM 으로 검증이 실제로 통과하는지, 그리고 mbedTLS 클라이언트가 쓸 TLS1.2 ECDHE-RSA-AES-GCM 을 서버가 받아주는지 확인한다.
# (a) 우리 trust PEM 으로 체인 검증 통과 여부
openssl s_client -connect "$HOST:443" -servername "$HOST" \
-CAfile cafe24_root.pem </dev/null 2>/dev/null \
| grep -E "Verify return code|Verification"
# 기대: "Verify return code: 0 (ok)"
# (b) mbedTLS 가 협상할 cipher/프로토콜 강제 확인 (TLS1.2 + ECDHE-RSA-AES-GCM)
openssl s_client -connect "$HOST:443" -servername "$HOST" \
-tls1_2 -cipher 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384' \
-CAfile cafe24_root.pem </dev/null 2>/dev/null \
| grep -E "Protocol|Cipher|Verify return code"
# 기대: Protocol = TLSv1.2, Cipher = ECDHE-RSA-AES(128|256)-GCM-...
# (c) 인증서 만료일 확인 (leaf + CA)
openssl s_client -connect "$HOST:443" -servername "$HOST" </dev/null 2>/dev/null \
| openssl x509 -noout -dates -subject -issuer
openssl x509 -in cafe24_root.pem -noout -dates -subject
펌웨어 mbedtls_config.h 가 ECDHE-RSA + AES-GCM(TLS1.2)만 켜므로, 위 (b) 가
실패하면(서버가 해당 cipher 미지원) TLS 연결 불가다 → 서버 측 cipher
설정을 맞추거나 mbedtls_config.h 의 허용 cipher/곡선을 조정해야 한다.
4. server_ca.c 재생성
검증을 통과한 cafe24_root.pem 으로 C 소스를 재생성한다(빌드 머신, PowerShell).
# firmware/certs 디렉터리에서
./gen_ca_header.ps1 -PemPath cafe24_root.pem
# 또는 출력 경로 지정:
./gen_ca_header.ps1 -PemPath C:\path\cafe24_root.pem -OutPath .\server_ca.c
스크립트는 PEM 각 라인을 "...\n" C 리터럴로 변환하고,
SERVER_CA_PEM_LEN = sizeof(SERVER_CA_PEM) 로 정의한다(종결 NUL 포함 —
mbedTLS PEM 파싱 요구사항). 생성 후 재빌드하면 새 CA 가 적용된다.
생성 결과를 빠르게 sanity-check:
# 생성된 .c 안의 PEM 이 다시 openssl 로 파싱되는지(따옴표/escape 깨짐 검출용)
grep -oE 'BEGIN CERTIFICATE|END CERTIFICATE' server_ca.c
5. 만료 → 펌웨어 업데이트 (위험 R3)
폐쇄망이라 인증서를 OTA 로 갱신할 수 없다. 임베드한 root/intermediate CA 가 만료되거나 서버가 발급기관/체인을 교체하면 TLS 핸드셰이크가 실패하고 보고가 중단된다.
대응:
- 만료일 모니터링: 위 3-(c) 로 CA
notAfter를 기록·달력 알림 설정. - 사전 교체: 만료 1~3개월 전 새 CA 로
gen_ca_header.ps1재생성 → 재빌드. - 재배포 절차: 폐쇄망 반입 절차(USB/오프라인 번들)로 새
.bin/.hex플래싱(sht30_fw단일 타깃).firmware/VERSION갱신. - 여유 신뢰 앵커: 가능하면 차기 root 까지 함께 임베드(여러 CA 연결)하여 교체 윈도를 넓힌다(여분 CA 는 검증에 무해).
참고:
docs/stm32f407_migration_plan.md위험표 R3 — "Cafe24 인증서 cipher/체인/만료 → TLS 연결 불가 → 사전openssl s_client로 체인·cipher 확인, 루트 CA 임베드, 만료 시 펌웨어 업데이트 절차".