docs: design the EXE installer UI and code signing scope

- Design 14: WiX MSI + Burn with a C# BA host and WebView2 web UI,
  default screen catalog, installer-ui.json and the WebView2 fallback.
- Design 16: Smart App Control checks and what must be signed.
- Manual: beginner guide to Azure Artifact Signing.
- Link the decision from designs 09 and 10 and record it.

Refs #15

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jychoi4andClaude Opus 5.5 committed 2026-11-11 11:08:50 +09:00
1 parent aba06b3443
commit 7f5540f148
6 files changed
+672

No files matched your search

@@ -2,6 +2,8 @@
2026-10-19 실행 범위 변경: 이 문서의 EXE 설치 과정·BA·HTML 페이지 편집은 후속 범위다. 첫 제품의 Tauri UI는 MSIX 제작 설정을 편집하며 설치 마법사를 생성하지 않는다. [현재 설계](12-msix-first-builder.md) 참조.
2026-11-09 결정: EXE 설치 UI는 C# BA 호스트와 WebView2 웹 UI로 만들고 기본 화면 카탈로그를 제공한다. 아래 "Rust/Tauri 사용자 정의 설치 UI" 절과 "기본 테마 편집 → 사용자 정의 BA" 구현 순서는 [14 EXE 설치 UI](14-exe-installer-ui.md)가 대체한다.
이 문서의 최종 사용자 설치 과정·마법사·Burn BA 커스터마이징은 **EXE 출력 전용**이다. MSIX는 Windows/Store가 설치 UI와 경로를 관리하며 제작기는 manifest의 이름·아이콘·지원 선언을 편집한다. 두 형식의 설정을 혼합하거나 미지원 단계를 조용히 생략하지 않는다. [범용 출력 계약](11-generic-exe-msix-architecture.md) 참조.
## 목표
@@ -8,6 +8,8 @@
현재 결과는 상호작용 시안과 구현 설계다. 실제 설치·백업·서명 검사·관리자 승격·재부팅은 실행하지 않으며 표시되는 경로·진행률·검증 결과는 예시다.
2026-11-09: 기본으로 제공할 화면 ID·숨김 규칙·1단계 화면 10개와 `installer-ui.json` 형식은 [14 EXE 설치 UI](14-exe-installer-ui.md)를 따른다. 아래 `prepare`·`apply`는 14번의 `filesInUse`·`progress`에 대응한다.
## 참고한 패턴
| 출처 | 확인한 특징 | EG-BIM에 적용한 설계 |
+245
View File
@@ -0,0 +1,245 @@
# EXE 설치 UI: WebView2 BA와 기본 화면
결정일: 2026-11-09. EXE 설치 파일(`setup.exe`)의 화면을 사용자가 자유롭게 꾸밀 수 있게 하는 구조와 기본으로 제공할 화면 목록을 정한다. 이 문서는 설계이며 BA·웹 UI 구현, Burn 연결, Windows 설치 시험은 수행하지 않았다. MSIX는 Windows가 설치 UI를 관리하므로 이 문서의 대상이 아니다.
[09 설치 과정과 UI 편집](09-install-flow-and-ui.md)의 단계·데이터 계약과 [10 설치 마법사 재설계](10-installer-wizard-redesign.md)의 모드별 흐름·실패 처리 원칙은 유지한다. 아래 결정이 두 문서와 다르면 이 문서를 따른다.
## 결정
| 항목 | 결정 |
| --- | --- |
| 설치 패키지 | WiX로 만든 MSI를 Burn 번들(`setup.exe`)로 감싼다. WiX 버전과 이용 조건(OSMF)은 채택 전에 확정한다. |
| 설치 UI | **사용자 정의 Burn BA**를 사용한다. BA 호스트는 C#(.NET Framework 4.8)로 만들고 화면은 **WebView2가 표시하는 HTML/CSS/TypeScript 웹 UI**로 만든다. |
| 제품별 차이 | BA 호스트와 웹 UI는 공통으로 한 번 빌드·서명한다. 제품마다 다른 화면 설정은 `installer-ui.json`과 이미지·약관 자산으로만 전달한다. |
| 기본 화면 | 아래 [기본 화면 카탈로그](#기본-화면-카탈로그)를 표준으로 제공한다. 제작자는 문구·자산·표시 여부·일부 순서를 편집한다. |
| WebView2가 없을 때 | 같은 BA 안의 단순한 네이티브 기본 화면(WPF)으로 설치를 진행한다. 인터넷이 되면 WebView2 웹 부트스트래퍼를 BA 선행 패키지로 먼저 설치할 수 있다. 고정 버전 런타임 포함은 기본값이 아니다. |
| 무인 설치 | `/quiet`·`/passive`에서는 WebView2를 만들지 않고 같은 검증과 기본값으로 설치한다. |
| 제외 | Rust/Tauri로 BA를 직접 구현하는 방식(09의 "Rust/Tauri 사용자 정의 설치 UI")과 WixStdBA 테마 편집을 첫 구현으로 삼는 순서는 채택하지 않는다. WiX가 C++·.NET BA API만 공식 제공하기 때문이다. |
## 구조
```text
setup.exe (Burn 번들, 서명)
├─ Burn 엔진 ........ 탐지 → 계획 → 적용, UAC 권한 상승, 롤백, 프로그램 추가/제거 등록
├─ BA 호스트 (C#, .NET Framework 4.8)
│ ├─ WebView2 ── 웹 UI 표시
│ ├─ 기본 네이티브 화면 ── WebView2를 쓸 수 없을 때
│ └─ 브리지 ── 웹 UI ↔ Burn 메시지 중계, 입력 재검증
├─ 웹 UI (Vite 빌드 결과: HTML/CSS/JS)
├─ installer-ui.json + 로고·이미지·약관
├─ 선행 프로그램 패키지
└─ 제품 MSI
```
WiX v5부터 Burn은 BA를 별도 실행 파일로 띄운다. BA 호스트는 WiX가 제공하는 .NET BA 라이브러리로 Burn과 통신한다. 실제 파일·레지스트리 변경은 Burn과 MSI가 수행하며 BA나 웹 UI가 `msiexec`를 따로 실행하지 않는다.
Installer Studio도 WebView2(Tauri)를 쓰므로 같은 웹 UI에 가짜 브리지를 연결해 제작 화면에서 미리보기를 제공한다. 미리보기는 시스템을 변경하지 않으며, 최종 판정은 실제 번들의 VM 시험으로 한다.
## 실행 흐름
1. Burn이 BA 호스트를 실행한다. 대화형 모드면 호스트가 창을 만들고 WebView2 사용 가능 여부를 확인한다.
2. 웹 UI는 번들 안의 파일만 가상 호스트(`https://installer.local/`)로 읽는다. 외부 주소 이동, 새 창, 다운로드는 차단한다.
3. Burn 탐지 결과(설치 여부, 설치된 버전, 기능 상태, 선행 프로그램 필요 여부)를 웹 UI에 보내고, 웹 UI는 신규 설치·업그레이드·유지 관리 중 맞는 흐름을 연다.
4. 사용자가 `review`에서 시작을 누르면 웹 UI가 `apply` 메시지를 보낸다. 호스트는 경로 규칙, 허용 기능, 약관 동의를 다시 검사한 뒤 Burn 변수를 설정하고 계획·적용을 요청한다.
5. Burn의 진행·오류·재부팅·취소 이벤트를 웹 UI에 전달한다. 적용이 시작된 뒤에는 선택값과 계획을 바꾸지 않는다.
6. 완료 화면의 "앱 실행"은 원래 사용자 권한으로 실행한다.
## 브리지 메시지
웹 UI는 아래 형식의 메시지만 보낼 수 있다. 호스트는 알 수 없는 `type`, 형식이 틀린 값, 현재 상태에서 허용되지 않는 요청을 거부한다.
| 방향 | `type` | 내용 |
| --- | --- | --- |
| 호스트 → UI | `init` | `installer-ui.json`, 선택 언어, 실행 모드(대화형) |
| 호스트 → UI | `detected` | 설치 상태(`absent`/`older`/`same`/`newer`), 설치된 버전·경로, 기능 상태, 필요한 선행 프로그램, 차단 사유 |
| 호스트 → UI | `validation` | 입력 검증 결과(필드별 오류 코드와 문구) |
| 호스트 → UI | `progress` | 전체 진행률, 현재 패키지·작업 이름 |
| 호스트 → UI | `filesInUse` | 사용 중인 프로그램 목록 |
| 호스트 → UI | `result` | `succeeded`/`failed`/`cancelled`, 재부팅 필요 여부, 오류 코드, 로그 경로 |
| UI → 호스트 | `validate` | 설치 폴더, 기능 선택 등 입력 검증 요청 |
| UI → 호스트 | `browseFolder` | 폴더 선택 대화상자 요청. 결과 경로도 호스트가 검증 |
| UI → 호스트 | `apply` | 동작(`install`/`modify`/`repair`/`uninstall`), 설치 폴더, 기능, 추가 작업, 약관 동의 |
| UI → 호스트 | `cancel` | 취소 요청. 처리 결과는 `result`로 확인 |
| UI → 호스트 | `retryFilesInUse` / `closeApplications` | 사용 중인 프로그램 다시 확인. 강제 종료는 기본 동작이 아니다. |
| UI → 호스트 | `launchApp`, `openLink`, `restart`, `close` | 완료 후 작업. `openLink`는 `installer-ui.json`에 등록된 주소만 허용 |
## 기본 화면 카탈로그
모든 화면은 공통 틀(상단 로고·제품명·단계 표시, 본문, 하단 이전/다음·실행/취소 버튼)을 쓴다. 언어는 한국어·영어를 기본으로 하고 시스템 언어로 자동 선택한다.
### 신규 설치 흐름
| 순서 | 화면 ID | 내용 | 숨김 |
| --- | --- | --- | --- |
| 1 | `welcome` | 제품명·버전·로고, 안내 문구. 이전 버전이 있으면 업그레이드 안내 | 가능 |
| 2 | `license` | 약관 전문과 동의. 동의해야 다음으로 진행 | 약관이 없을 때만 |
| 3 | `installFolder` | 기본 설치 폴더, 찾아보기, 필요·사용 가능 공간 | 가능. 숨기면 기본 경로 |
| 4 | `features` | 기능 트리와 용량. 필수 기능은 해제 불가 | 가능. 숨기면 기본 선택 |
| 5 | `tasks` | 바탕화면 바로가기, 파일 연결 등 선택 작업 | 가능. 숨기면 기본값 |
| 6 | `review` | 경로, 기능, 선행 프로그램, 필요한 권한 요약과 시작 버튼 | 불가 |
| 7 | `progress` | 현재 작업, 전체 진행률, 취소 | 불가 |
### 유지 관리 흐름
이미 설치된 PC에서 `setup.exe`를 실행하거나 프로그램 추가/제거에서 변경을 선택할 때 사용한다.
| 화면 ID | 내용 | 다음 화면 |
| --- | --- | --- |
| `maintenance` | 수정, 복구, 제거 선택 | 수정: `features` → `review`, 복구·제거: `review` 또는 `uninstallConfirm` |
| `uninstallConfirm` | 제거할 항목과 남는 항목(사용자 도면·설정·백업) 안내, 확인 | `progress` |
### 공통 상태 화면
조건에 따라 표시하며 숨길 수 없다.
| 화면 ID | 조건 | 내용 |
| --- | --- | --- |
| `blocked` | 시작 전 검사 실패 | 더 높은 버전 설치됨, 지원하지 않는 OS, 디스크 부족 등 원인과 조치 |
| `filesInUse` | 설치할 파일을 쓰는 프로그램이 있음 | 저장 후 종료 안내, 다시 확인 |
| `finish` | 성공 | 결과 요약, 앱 실행 선택, 링크 |
| `rebootRequired` | 성공했지만 재부팅 필요 | 이유, 지금/나중에 선택. 기본은 나중에 |
| `failure` | 오류 | 실패 단계, 기존 제품 상태, 로그 위치, 다음 조치 |
| `cancelled` | 사용자 취소 | 취소 시점과 엔진이 확인한 실제 상태 |
### 편집 규칙
- 화면을 숨겨도 그 단계의 작업은 기본값으로 실행한다. 숨김은 표시 여부만 바꾼다.
- `license`는 약관이 지정되어 있으면 숨길 수 없다. 숨김을 동의로 처리하지 않는다. 무인 설치의 동의 전달은 별도 인자로 정한다.
- 순서 변경은 `welcome`~`tasks` 사이의 선택 화면에만 허용한다. `review` 이후와 상태 화면은 고정한다.
- 표시하는 입력은 실제 설치 정의와 연결되어야 한다. 예를 들어 `tasks`의 바로가기 항목은 MSI 바로가기 정의가 있어야 하며, 연결이 없으면 빌드 오류로 처리한다.
| 대상 | 편집 가능 항목 |
| --- | --- |
| 모든 화면 | 제목, 설명, 다국어 문구, 로고, 강조 색, 밝기 테마 |
| `welcome` | 대표 이미지, 안내 문구 |
| `license` | 약관 파일(RTF/TXT/HTML), 동의 문구 |
| `installFolder` | 기본 경로(프로젝트 `formats.exe.installRoot`), 경로 변경 허용 여부 |
| `features` | 기능 이름·설명·기본 선택·필수 여부 |
| `tasks` | 항목 표시, 기본 선택 상태 |
| `finish` | 앱 실행 표시와 기본 선택, 링크 목록 |
## installer-ui.json
빌드 시 프로젝트 설정에서 생성해 번들에 넣는 파일이다. 제작기에서는 버전 메타파일의 `installerUi`로 편집하며([제작기 설치화면 편집](#제작기-설치화면-편집-2026-11-09)), 이 절은 번들에 넣는 생성 결과의 형식을 정한다. 자산 경로는 프로젝트 폴더 기준의 안전한 상대 경로만 허용한다.
```json
{
"schemaVersion": 1,
"template": { "id": "standard-wizard", "version": "1.0.0" },
"theme": {
"logo": "assets/logo.png",
"welcomeImage": "assets/welcome.png",
"accentColor": "#7257C5",
"colorScheme": "dark"
},
"locales": { "default": "ko-KR", "available": ["ko-KR", "en-US"] },
"strings": {
"ko-KR": { "welcome.title": "EG-BIM 설치를 시작합니다" },
"en-US": { "welcome.title": "Welcome to EG-BIM Setup" }
},
"pages": [
{ "id": "welcome", "visible": true },
{ "id": "license", "visible": true, "file": { "ko-KR": "assets/license.ko.rtf", "en-US": "assets/license.en.rtf" } },
{ "id": "installFolder", "visible": true, "allowChange": true },
{ "id": "features", "visible": false },
{ "id": "tasks", "visible": true },
{ "id": "review" },
{ "id": "progress" }
],
"finish": {
"launchApp": { "visible": true, "defaultChecked": true },
"links": [{ "label": "release.notes", "url": "https://example.com/egbim/release-notes" }]
}
}
```
- 알 수 없는 필드, 카탈로그에 없는 화면 ID, 숨길 수 없는 화면의 `visible: false`, 허용 범위 밖의 순서는 빌드 전에 거부한다.
- 상태 화면은 `pages`에 적지 않는다. 문구만 `strings`로 바꾼다.
- `url`은 `https`만 허용하고 `openLink` 요청은 이 목록과 비교한다.
- 빌드 스냅샷에는 `installer-ui.json`과 모든 자산의 SHA-256을 기록한다. 서명한 `setup.exe`의 화면을 외부 파일로 바꾸지 않는다.
## 제작기 설치화면 편집 (2026-11-09)
설치 구성 › **설치화면** 탭에서 버전마다 설치 단계·화면 문구·모양·스크립트를 편집한다. 값은 버전 메타파일(`.isver`)의 `installerUi`에 저장하고, 버전을 추가하면 함께 복사하며, 잠긴 버전은 읽기 전용이다. 이 필드가 없는 기존 버전 파일은 아래 표준 마법사로 연다. 빌드 시 이 값에서 `installer-ui.json`과 MSI 사용자 지정 동작을 만든다(EXE 빌드와 함께 후속 구현).
| 항목 | 규칙 |
| --- | --- |
| 표준 마법사 | `welcome`, `installFolder`, `review`, `progress`, `finish` 다음에 `maintenance`, `uninstallConfirm`, `failure`, `cancelled`. 이 9개는 지울 수 없다. |
| 단계 추가 | `license`(한 번만, 약관 파일 필수), `info`(안내 화면: 제목·설명), `input`(입력 화면). 추가한 단계는 `review` 바로 앞에 들어가며 삭제할 수 있다. |
| 순서 | `welcome`·`license`·`installFolder`·`info`·`input`만 `review` 앞에서 순서를 바꾼다. `review`·`progress`·`finish`와 유지 관리·결과 화면은 고정이다(`InvalidPageOrder`). |
| 숨김 | `welcome`·`installFolder`·`info`·`input`만 숨긴다. 숨겨도 그 단계는 기본값으로 진행한다(`PageCannotBeHidden`). |
| 문구 | 제목·설명은 서식 문자열이며 `[ProductName]` 같은 속성문자를 쓴다. 정의되지 않은 속성은 `UnknownProperty`. |
| 입력 화면 | 항목마다 저장할 사용자 속성(프로젝트 설정 › 속성문자), 이름, 형식(`text`·`checkbox`·`choice`), 기본값, 선택 목록, 필수 여부를 둔다. 같은 속성을 두 항목이 쓰지 않는다(`DuplicateInputProperty`). 입력값은 레지스트리·바로가기·스크립트 인수에서 `[속성]`으로 쓴다. |
| 모양 | 밝기(`dark`/`light`), 강조 색(`#RRGGBB`), 로고(프로젝트 폴더 기준 PNG·JPG·SVG). 약관 파일은 RTF·TXT·HTML. 파일이 없으면 `MissingAsset`. |
| 설치 폴더 | `fixedFolder`를 켜면 기본 설치 폴더를 바꿀 수 없다. |
### 스크립트
| 항목 | 규칙 |
| --- | --- |
| 종류 | `powershell`(.ps1), `cmd`(.cmd·.bat), `exe`(.exe). 확장자가 다르면 `ScriptKindMismatch`. 화면에 스크립트 본문을 입력하지 않는다. |
| 파일 | 프로젝트 소스(alias) 폴더 안의 `/` 경로. 설치 파일과 별도로 번들에 포함하므로 설치되지 않는 파일도 쓸 수 있다. 없으면 `MissingScriptFile`. 빌드 스냅샷에 SHA-256을 기록한다. |
| 실행 시점 | `beforeFiles`(설치: 파일 복사 전), `afterFiles`(설치: 파일 복사 후), `beforeUninstall`(제거: 파일 삭제 전)은 MSI 지연 사용자 지정 동작으로 설치 엔진 권한(관리자)으로 실행한다. `afterFinish`(완료 후)는 BA가 완료 화면 뒤에 로그인 사용자 권한으로 실행한다. |
| 인수 | 한 줄에 하나씩 배열로 전달하고 명령 문자열로 합치지 않는다. PowerShell은 `powershell.exe -NoProfile -ExecutionPolicy Bypass -File <파일> <인수...>`, CMD는 `cmd.exe /d /c <파일> <인수...>` 형태로 실행하며 인수는 따옴표 규칙에 맞게 하나씩 이스케이프한다. 각 인수는 서식 문자열이다. |
| 실패 처리 | `fail`은 0이 아닌 종료 코드·제한 시간 초과에서 설치를 중단하고 설치 엔진의 변경을 되돌린다. 스크립트가 직접 바꾼 내용은 되돌리지 않는다. `ignore`는 로그에 남기고 계속한다. `afterFinish`는 `ignore`만 허용한다(`ScriptFailureAfterFinish`). |
| 제한 시간 | 1~3600초(기본 600). 시간이 지나면 프로세스 트리를 종료한다. |
| 로그 | 스크립트 이름, 시점, 종료 코드, 소요 시간을 설치 로그에 남긴다. 출력에 비밀 값을 쓰지 않도록 안내한다. |
패치 버전도 같은 스크립트를 가질 수 있으며 패치 적용 시 실행된다. 스크립트 실행은 Windows VM 설치·제거·패치 시험 전에는 검증된 것으로 표시하지 않는다.
## WebView2 가용성
Windows 11과 대부분의 Windows 10에는 WebView2 Evergreen 런타임이 있지만 LTSC, Server, 업데이트가 막힌 오프라인 PC에는 없을 수 있다.
| 상황 | 동작 |
| --- | --- |
| WebView2 있음 | 웹 UI 표시 |
| 없음 + 인터넷 사용 가능 | 번들 설정이 허용하면 WebView2 웹 부트스트래퍼를 BA 선행 패키지로 설치한 뒤 웹 UI 표시. 실패하면 기본 네이티브 화면 |
| 없음 + 오프라인 | 기본 네이티브 화면으로 같은 흐름 진행 |
| 무인 설치 | WebView2를 사용하지 않음 |
기본 네이티브 화면은 같은 카탈로그의 필수 화면(`welcome`, `license`, `installFolder`, `review`, `progress`, 상태 화면)과 같은 검증을 제공하며 디자인 편집은 문구·로고로 제한한다. WebView2 사용자 데이터 폴더는 실행마다 임시 폴더에 만들고 종료 시 삭제한다.
## 보안
- 웹 UI는 번들 안 정적 파일만 사용한다. 원격 스크립트, 외부 CDN, `eval`을 쓰지 않고 CSP는 `default-src 'self'`로 둔다.
- 릴리스 빌드는 개발자 도구, 상황 메뉴, 확대·축소 단축키를 끈다.
- 웹 UI는 파일·레지스트리·프로세스에 직접 접근하지 않는다. 모든 요청은 정해진 메시지로 호스트를 거치고 호스트가 다시 검증한다.
- BA와 웹 UI는 일반 사용자 권한으로 실행한다. 권한 상승은 Burn이 관리하며 UAC 화면은 꾸미지 않는다.
- 로그와 메시지에 토큰·비밀번호를 넣지 않는다.
## 1단계 범위와 검증 실험
첫 구현은 신규 설치·업그레이드·제거와 실패·취소를 확인할 수 있는 화면 10개로 한다.
`welcome`, `license`, `installFolder`, `review`, `progress`, `finish`, `failure`, `cancelled`, `maintenance`, `uninstallConfirm`
`features`, `tasks`, `filesInUse`, `rebootRequired`, `blocked`는 MSI 기능·바로가기·파일 연결을 프로젝트 모델에 넣는 다음 단계에서 추가한다.
본 구현 전에 백로그 BL-08의 검증 실험을 VM에서 수행한다. MSI 1개와 선행 프로그램 1개를 담은 Burn 번들에 C# 호스트와 웹 UI를 붙이고 아래를 확인한다.
- 신규 설치, 같은 UpgradeCode의 상위 버전 업그레이드, 제거, 복구
- 설치 폴더 변경과 잘못된 경로 거부
- 진행률 표시, 적용 중 취소와 롤백 결과 표시
- 실패 화면과 로그 위치
- `/quiet` 무인 설치에서 WebView2 미사용
- WebView2가 없는 PC에서 기본 네이티브 화면으로 설치 완료
- 고해상도(DPI), 키보드 이동, 스크린 리더 읽기
실험 결과는 `src/tests/verification-YYYY-MM-DD-<slug>.json`에 실행·미실행 항목을 모두 기록한다. 실험 전에는 이 구조를 검증된 것으로 표시하지 않는다.
## 미결정 사항
- WiX 버전(v6 또는 v7)과 OSMF 이용 조건 수용 여부
- 지원 대상 PC 범위(Windows 10 LTSC·Server·오프라인 PC 포함 여부)와 WebView2 웹 부트스트래퍼 사용 허용 여부
- 약관 파일 형식과 무인 설치의 동의 전달 방식
- 기본 지원 언어 목록
- 기본 네이티브 화면을 WPF로 할지, 같은 호스트의 최소 Win32 화면으로 할지
## 근거
- [FireGiant — Out-of-process bootstrapper applications](https://docs.firegiant.com/wix/whatsnew/oopbas/): WiX v5부터 BA를 별도 실행 파일로 실행, C++·.NET BA API 제공
- [FireGiant — WixStdBA](https://docs.firegiant.com/wix/tools/burn/wixstdba/): 표준 BA 테마의 지원 범위
- [Microsoft Learn — Distribute your app and the WebView2 Runtime](https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution): Evergreen·고정 버전 런타임 배포 방식
- [Microsoft Learn — Overview of WebView2 features and APIs](https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/overview-features-apis): 가상 호스트 매핑, 웹 메시지 통신
- [Inno Setup — WizardStyle](https://jrsoftware.org/ishelp/topic_setup_wizardstyle.htm), [Revenera — Defining InstallShield Prerequisites](https://docs.revenera.com/installshield/helplibrary/IHelpSetPrereqCustom.htm): 기본 화면 구성 참고
@@ -0,0 +1,128 @@
# 16 스마트 앱 컨트롤 검사 대상과 서명 범위
2026-11-09 작성. 스마트 앱 컨트롤(Smart App Control, 이하 SAC)이 켜진 PC에서 설치한 제품이 동작하려면 무엇을 서명하고 무엇을 배포하지 말아야 하는지 정리한 참조 문서다. 서명 시스템(EXE 출력의 서명 단계, [05 릴리스 생성](05-release-pipeline.md))을 설계할 때 입력으로 쓴다. 이 문서는 조사 결과이며 구현이나 SAC 실제 시험 통과를 뜻하지 않는다. 정책 결정은 별도 변경 기록으로 남긴다.
## SAC 판정 순서
1. Microsoft 클라우드 평판 서비스가 안전 여부를 확신할 수 있으면 그 판정대로 허용하거나 차단한다. 유효한 서명이 있어도 악성 판정이면 차단된다.
2. 확신할 수 없으면 서명을 본다. 유효한 서명이 있으면 허용하고, 서명이 없거나 유효하지 않으면 차단한다.
3. 앱별·파일별 예외를 둘 수 없다. 사용자 선택지는 SAC를 끄는 것뿐이다. 최근 업데이트부터 끈 뒤 다시 켤 수 있다.
SmartScreen은 진입 실행 파일 하나만 평판을 보지만, SAC는 운영체제 로더와 스크립트 엔진이 불러오는 **모든 코드**를 검사한다. 서명된 설치기가 서명 없는 코드를 풀어 놓는 방식은 통과하지 못한다.
## 서명 요건
| 요건 | 내용 |
| --- | --- |
| 신뢰 루트 | Microsoft 신뢰 루트 프로그램(Trusted Root Program)에 속한 루트로 이어져야 한다. 사용자나 관리자가 직접 등록한 루트(개발용 인증서 포함)는 인정되지 않는다. |
| 다이제스트 | SHA-256 이상을 쓴다(`signtool sign /fd sha256`). |
| 키 알고리즘 | RSA를 쓴다. ECC는 지원되지만 호환성을 위해 피한다. |
| 타임스탬프 | SAC 자체 요건으로 명시된 자료는 없다. 인증서 만료 뒤에도 서명이 유효하도록 반드시 붙인다(`/tr <TSA URL> /td sha256`). |
| 서명 후 불변 | 서명 뒤 리소스·버전 정보를 수정하면 서명이 깨진다. 모든 후처리는 서명 전에 끝낸다. |
| 스크립트 서명 | `WinVerifyTrust`로 검증하며, 서명 루트가 대상 PC의 신뢰 루트 저장소에 있어야 한다. 공인 루트는 이 조건을 충족한다. |
## 검사 대상 분류
### A. 실행 코드(PE) — 반드시 서명
확장자가 아니라 파일 헤더(`MZ`와 PE 서명)로 판별한다. 운영체제 로더는 확장자와 관계없이 이미지를 불러올 때 검사한다.
- Microsoft가 명시한 확장자: `.exe`, `.com`, `.dll`, `.rll`, `.ocx`, `.sys`
- MotW 차단 목록에도 있는 PE 확장자: `.cpl`, `.drv`, `.scr`, `.vxd`
- 제품 고유 확장자 예(EG-BIM): `.irx`, `.tx`, `.txv`, `.txr`
- .NET 어셈블리(관리 코드 DLL)도 PE이므로 같은 기준으로 서명한다.
### B. Windows Installer — 서명
- `.msi`, `.msp`: 서명한다. MSI가 설치 중 임시 폴더에 푸는 사용자 지정 작업(Custom Action) DLL·EXE도 A와 같은 기준으로 미리 서명한다.
- `.mst`: App Control의 검사 대상이다. 서명 방법과 필요 여부는 확인이 필요하다.
### C. 패키지 — 패키지 서명
- `.appx`, `.appxbundle`, `.msix`, `.msixbundle`: 패키지 서명으로 처리한다. 패키지 안의 PE를 개별 서명해야 하는지는 SAC 시험으로 확인한다.
### D. 스크립트(App Control 연동 스크립트 호스트) — 서명하거나 배포하지 않음
| 호스트 | 확장자 | 서명이 없을 때 |
| --- | --- | --- |
| PowerShell | `.ps1`, `.psm1`, `.psd1` | 차단되지 않고 제한 언어 모드(Constrained Language Mode)로 실행된다. 기능이 조용히 실패할 수 있다. 진입 스크립트가 불러오는 모듈과 dot-source 파일도 모두 서명해야 한다. |
| Windows Script Host(`wscript.exe`, `cscript.exe`) | `.vbs`, `.js`, `.wsf` | 차단된다. |
| Windows Script Host(인코딩 스크립트) | `.vbe`, `.jse` | 배포하지 않는다. |
### E. 사용 금지
- `.hta`(`mshta.exe`): 스크립트 검사가 켜진 App Control 정책이 하나라도 있으면 모든 실행이 차단된다. SAC 환경에서도 같다고 보고 배포하지 않는다.
- MSXML을 통한 스크립트 실행(XSL 스크립트 등): 위와 같은 이유로 쓰지 않는다.
### F. 직접 통제 밖 — 서명 대상 아님, 단 실행되는 하위 프로그램은 검사됨
- `.bat`, `.cmd`(`cmd.exe`): App Control이 직접 통제하지 않으며 서명할 수도 없다. 배치 파일이 실행하는 EXE와 DLL은 검사된다.
- 앱이 스스로 해석하는 데이터·스크립트(LISP, CUI, DCL, 도면, 이미지 등): 검사 대상이 아니다.
- App Control과 연동하지 않는 타사 스크립트 엔진(Python, Java 등): 엔진 실행 파일은 A 기준으로 검사되지만, 그 엔진이 실행하는 스크립트는 통제되지 않는다.
### G. 인터넷에서 받은 파일(MotW)에 대한 차단 목록
웹 표식(Mark of the Web, MotW)이 붙은 아래 확장자는 열기 자체가 차단되고 무시 선택지가 없다. 목록은 고정이며 레지스트리로 바꿀 수 없다. 2026-04 기준 자료이므로 서명 시스템에는 갱신 가능한 데이터로 둔다.
`.appref-ms`, `.appx`, `.appxbundle`, `.bat`, `.chm`, `.cmd`, `.com`, `.cpl`, `.dll`, `.drv`, `.gadget`, `.hta`, `.iso`, `.js`, `.jse`, `.lnk`, `.msc`, `.msp`, `.ocx`, `.pif`, `.ppkg`, `.printerexport`, `.ps1`, `.rdp`, `.reg`, `.scf`, `.scr`, `.settingcontent-ms`, `.sys`, `.url`, `.vb`, `.vbe`, `.vbs`, `.vhd`, `.vhdx`, `.vxd`, `.wcx`, `.website`, `.wsf`, `.wsh`
배포와 업데이트에 적용하는 규칙:
- 배포물은 서명된 `.exe` 또는 `.msi`로 직접 내려받게 한다. `.iso`, `.vhd`, `.vhdx`로 감싸지 않는다.
- 업데이트가 받는 파일에 이 목록의 확장자를 쓰지 않는다. 패치는 서명된 설치 파일 형태로 받아 실행한다.
- MotW를 지워서 차단을 피하는 방식은 쓰지 않는다.
- 설치기가 디스크에 쓴 파일에는 보통 MotW가 붙지 않는다. 따라서 설치된 `.chm`, `.bat` 등은 이 목록의 영향을 받지 않는 것으로 본다. 이 판단은 SAC 시험으로 확인한다.
## 서명 시스템 반영 요구사항
1. **PE 판별**: 수집한 모든 파일을 헤더로 검사해 PE를 찾는다. 확장자 목록만으로 판정하지 않는다([05 릴리스 생성](05-release-pipeline.md)의 기존 원칙).
2. **확장자 분류 데이터**: B~G 분류를 코드에 고정하지 않고 버전이 붙은 데이터로 둔다. 분류 결과를 서명 보고서에 기록한다.
3. **파일별 정책**: 기존 `sign-owned`, `verify-vendor`, `not-applicable`을 유지한다. 서명 없는 타사 PE(오픈소스 등)를 처리할 정책은 결정이 필요하다.
4. **거부 규칙**: 정식 빌드에서 아래를 발견하면 설명과 함께 거부한다.
- 서명할 수 없거나 서명되지 않은 PE
- D 분류의 서명 없는 스크립트
- E 분류 파일
- 배포물 형식이 G 목록에 해당하는 경우
오류 코드는 구현 시 정하고 `src/README.md`에 적는다.
5. **서명 검증**: 서명 후 모든 대상 파일을 다시 검증하고 아래 항목을 기록한다. 허용할 루트 인증서의 지문은 계획에 고정한다.
- 다이제스트가 SHA-256 이상인지
- 체인과 루트 지문
- 타임스탬프 유무
- 서명자
6. **실행 중 생성 코드 점검표**: 정적 검사로 찾을 수 없으므로, 제품마다 아래 질문을 확인 항목으로 둔다.
- 업데이트가 내려받는 파일은 무엇인가
- 자체 압축 해제 EXE를 만드는 기능이 있는가
- 임시 폴더에 DLL을 풀어서 쓰는가
- 실행 중에 코드를 컴파일해 디스크에 저장하는가
## EG-BIM 조사 결과
2026-11-09 이 PC에 설치된 EG-BIM(`C:\Program Files\Baron\EG-BIM`)을 읽기 전용으로 조사했다. SAC를 켠 실제 실행 시험은 하지 않았다.
| 항목 | 결과 |
| --- | --- |
| PE 파일 | 965개. 서명 유효 620개, 서명 없음 344개, 판정 오류 1개(`IntelliConvert.com`) |
| 바론 서명 파일 | 6개(`Grimi.exe`, `Kkureomi.exe`, `LoadFile.exe`, `EGBIM.Drawer.Splash.exe`, `Nyng.PlotWindow.exe`, `PlotStyleTableEditor.exe`) |
| 바론 인증서 | GlobalSign GCC R45 CodeSigning CA 2020 발급, 루트 GlobalSign Code Signing Root R45(`AuthRoot` 저장소에 있음), 2026-08-10~2027-08-11, 온라인 폐기 확인 통과, 타임스탬프 있음 |
| 서명 없는 PE 확장자 | `.dll` 294, `.irx` 31, `.exe` 9, `.tx`·`.txv`·`.txr` 10, `.com` 1 |
| 그 밖의 SAC 관련 파일 | `.chm` 9개, `.bat` 1개(`Nyng.bat`). `.ps1`, `.vbs`, `.js`, `.hta`, `.msi`는 없음 |
| 실행 중 생성 코드 후보 | `Extractor.exe`(자체 압축 해제 스텁으로 보임), `BaronSoftware.Update.dll`(업데이트) |
## SAC 시험 항목(미실행)
SAC가 켜진 Windows 11 VM 스냅샷에서 실행하고 `CodeIntegrity/Operational`(3076, 3077, 3089)과 `AppLocker/MSI and Script`(8028, 8029, 8037, 8038) 이벤트를 수집한다.
- 모든 PE를 서명한 빌드의 설치·실행·주요 기능·업데이트·제거
- `.irx`, `.tx` 등 비표준 확장자 PE가 검사·차단되는지
- 서명된 `.dll`, `.msp`에 MotW가 붙었을 때 G 목록 차단이 서명으로 풀리는지
- MSIX 내부 PE를 개별 서명하지 않아도 되는지
- 설치된 `.chm`, `.bat`이 영향받지 않는지
- 첫 실행과 두 번째 실행의 시작 시간 차이
## 근거
- [What is Smart App Control? — Microsoft Support](https://support.microsoft.com/en-us/topic/what-is-smart-app-control-285ea03d-fa88-4d56-882e-6698afdb7003): 판정 순서, 예외 불가, 재활성화
- [App Control for Business feature availability — Microsoft Learn](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/feature-availability): 검사 대상 파일 형식
- [Understand App Control script enforcement — Microsoft Learn](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/script-enforcement): 스크립트 호스트별 동작, `cmd.exe` 비통제, 이벤트 ID
- [Smart App Control — text/plain(Eric Lawrence, Microsoft Defender 소속 개인 블로그, 2026-04-28)](https://textslashplain.com/2026/04/28/smart-app-control/): MotW 차단 목록, 서명 요건. 공식 문서가 아니므로 시험으로 확인한다.