---
title: 系統完整性鎖定 → 解鎖 SOP（原廠側）
nav: 解鎖 SOP
---

# 系統完整性鎖定 → 解鎖 SOP（原廠內部作業文件）

> **適用對象**：原廠客服／技術支援／授權簽發人員（坐在 License Center 後台前受理客戶回報的人）。
> **本文用途**：客戶回報系統鎖定後，從研判到簽出 unlock token、交付客戶的完整作業流程。
> **不在本文範圍**：防竄改機制的設計理由與決策（見同資料夾 `design.md` §2 D5/D6/D11、§4.2–4.4）；客戶端操作（見 `docs/user-manual/system-integrity-lockdown-guide.md`）。
> ⚠️ **本文不含任何密碼、passphrase、token 值**。需要 passphrase 時一律查部署文件或 `.env`。

**變更紀錄**

| 日期 | 內容 |
|------|------|
| 2026-08-16 | 首版（CM-1239）：研判流程、LC 後台頁與 CLI 兩種簽發路徑、token 三性質與過期重簽、交付與失敗排查 |

---

## 一、這件事的全貌

Guidant AI 落地部署版在啟動時與運行期間會比對產物檔案的 SHA-256 與原廠簽章 manifest。**比對失敗即進入鎖定**：正常服務不啟動，改由一個極簡的 lockdown 殼回應所有請求（HTTP 503），客戶端看到一頁「系統已鎖定」。

**解鎖的唯一途徑，是原廠用 License Center 私鑰簽出的一次性 unlock token**。沒有其他後門、沒有旁路開關——這是刻意的設計（`design.md` D6/D8）。

```
客戶端偵測到完整性驗證失敗
        │
        ▼
服務鎖定（lockdown 殼回 503，畫面顯示事件編號）
        │
        ▼
客戶透過人工通道（電話／郵件）回報 ── 提供 machine_fingerprint ＋ tamper_event_id
        │
        ▼
【原廠】研判（誤鎖 or 真竄改）→ 兩者都走同一條簽發路徑，但研判紀錄要留
        │
        ▼
【原廠】License Center 簽發 unlock token（後台頁 或 CLI）
        │
        ▼
交付客戶（token 檔）
        │
        ▼
客戶把檔案放進主機 pki 目錄 → docker restart → 啟動時核銷四關 → 解鎖
```

> 🔴 **解鎖不豁免當次的完整性驗證。** 核銷成功後啟動流程仍會往下跑完整 manifest 比對——**被改動的檔案若沒還原，會立刻再次鎖定**（新的事件編號、新的 token）。因此交付 token 前必須先確認客戶已把產物還原（或已重新部署原廠 image）。

---

## 二、前置研判

### 2.1 兩種情況、同一條路徑

| 情況 | 典型徵兆 | 處置 |
|------|---------|------|
| **誤鎖** | 客戶未做任何主機層操作；或磁碟／檔案系統異常、部署過程中斷、image 版本與 manifest 版本不匹配 | 簽發 token，並排查根因避免再犯 |
| **真竄改** | 客戶確認有人以 SSH／`docker exec` 動過產物；或不符檔案的 `mtime` 落在可疑時間點 | 依商務／法務流程處理後，視決策簽發 |

**兩者走同一條簽發路徑**（D6 定案）——技術上不做區分，因為機器端無從自證。**差別在紀錄**：簽發表單的「研判備註」欄必須寫清楚 ①客戶回報管道與時間 ②判定為誤鎖或真竄改 ③判定依據。這欄會與 token 一起落進 License Center 的 `unlock_token_issuance` 稽核帳（append-only），日後「誰為什麼簽了什麼」要答得出來。

### 2.2 邊界：不要承諾查明「是誰改的」

產品**沒有、也不可能有**「是誰改了檔案」這個答案——竄改動作走 SSH／`docker exec` 等主機層管道，不經過任何業務邏輯（`design.md` §4.4.3）。

系統蒐集到的是**檔案證據**（不符檔案的 `mtime`／`ctime`／`owner`／`size`）與**若剛好有業務 session 在跑時的旁證**，不是身分溯源。對客戶溝通時：

- ✅ 可以說：「產品完整性驗證失敗，事件編號 evt-…，不符檔案 N 個，最近修改時間為 …」
- ❌ 不要說：「我們查到是誰改的」「這是某某帳號做的」

契約層面綁的是「完整性驗證失敗即構成違約事由」這個**客觀事實**，不以查明具體操作人員為前提（措辭見 `design.md` §4.4.3）。

### 2.3 蒐集研判材料

要客戶提供（或自行從回報事件查）：

| 材料 | 來源 |
|------|------|
| `machine_fingerprint`（64 hex） | 客戶端鎖定畫面／伺服器啟動 log 的 `machine_fingerprint:` 那一行 |
| `tamper_event_id`（`evt-` 開頭） | 同上，`tamper_event_id:` 那一行 |
| 不符檔案清單與 metadata | 客戶端 `docker logs <容器>` 的 `[FATAL]` 區塊；或 License Center 收到的 D9 回報事件（有網環境才有） |
| 客戶端最近的操作紀錄 | 客戶自述（部署、升級、手動改檔） |

> **D9 在線回報**：客戶端偵測到竄改時會 best-effort 把事件 POST 給 License Center（`POST /api/internal/report-tamper`，存於 `tamper_events` 表）。**發得出就發、發不出不重試**——所以「LC 這邊查不到事件」不代表客戶沒被鎖定，離線環境本來就送不出來。

---

## 三、簽發 unlock token（路徑 A：LC 後台頁，常用）

### 3.1 進入位置

License Center 後台 → 導覽列「**解鎖 token**」（路徑 `/unlock`），需登入。

服務位址：`http://<LC 主機>:5062`（STG 在 188 `/opt/license_center`，systemd unit `license-center.service`）。

### 3.2 填寫欄位

| 欄位 | 必填 | 說明 |
|------|------|------|
| **機器指紋（machine_fingerprint）** | ✅ | 客戶回報的 64 個十六進位字元。**須完全一致**——這是 token 綁機的依據 |
| **竄改事件編號（tamper_event_id）** | ✅ | `evt-` 開頭。token 只解得開這一次事件 |
| **效期（小時）** | ✅ | 預設 72 小時，允許範圍 1～336（14 天） |
| **簽發人** | ✅ | 自己的名字／工號，落稽核帳 |
| **研判備註** | — | 見 §2.1，強烈建議填 |
| **私鑰 passphrase** | 視部署 | **僅在該 LC 站台未於環境設定中配置 passphrase 時才出現此欄**；已配置時系統自動解鎖私鑰，表單不顯示這一欄。passphrase 值請查該站台的部署設定，本文不記載 |

**服務端守門**（三個入口共用，抄漏一段會在這裡就被擋下）：

- 機器指紋必須是 64 個十六進位字元
- 事件編號必須以 `evt-` 開頭
- 效期必須在 1～336 小時之間

> 這些守門刻意放在服務層而非表單層：抄漏一段的值若放行簽出，客戶端拒收的原因會顯示成「這是為另一台機器簽發的 token」，現場查不出真因。

### 3.3 簽發後

送出成功會跳到結果頁，顯示 `token_id`、有效期限與完整 payload。按「**下載**」取得檔案：

- 檔名固定 `unlock.token`（客戶下載完直接丟進目錄，不必改名）
- 內容是 v2 信封裸 JSON，**刻意不套 v3 armor**——armor 標頭寫的是 `GUIDANT LICENSE`，套上去會讓客戶誤以為是授權照而拿去「授權管理」頁上傳

下載動作本身也落 log（誰拿走了哪一張）。

---

## 四、簽發 unlock token（路徑 B：CLI，air-gapped 保底）

簽發站的 Web 後台不可達，或作業環境完全離線時使用。**與後台頁同一條 code path、同一把私鑰、同樣寫稽核帳**。

在 LC 部署目錄（如 `/opt/license_center`）執行：

```bash
poetry run python -m license_center.cli.main issue-unlock-token \
  --machine-fingerprint <客戶回報的 64 hex> \
  --tamper-event-id evt-xxxxxxxx \
  --issuer <簽發人> \
  --valid-hours 72 \
  --remark "客戶電話回報；判定為部署中斷造成的誤鎖" \
  --output /tmp/unlock.token
```

| 參數 | 說明 |
|------|------|
| `--machine-fingerprint` | 必填，64 hex |
| `--tamper-event-id` | 必填，`evt-` 前綴 |
| `--issuer` | 必填 |
| `--valid-hours` | 選填，預設同後台頁（72） |
| `--remark` | 選填，研判備註 |
| `--private-key-path` | 選填，預設值即標準私鑰路徑 |
| `--output` | 選填；未指定則把 token 印到 stdout |

執行時會**互動式要求輸入私鑰 passphrase**（不接受命令列參數帶入，避免落進 shell history）。

> ⚠️ **簽出 token 等於解除客戶端該次鎖定**——請先完成研判再執行。

指定 `--output` 時，CLI 會提示：交付客戶後請其將檔案更名為 `unlock.token` 放進竄改標記目錄（預設 `/opt/guidant/pki/`）並重啟服務。

---

## 五、token 的三個性質（跟客戶說明時的重點）

| 性質 | 機制 | 對客戶的意義 |
|------|------|------------|
| **一次性** | payload 內含 64 hex 隨機 `nonce`；核銷成功後記進客戶端的 `used-nonces.json`，同一張再用會被拒 | 用過就不能留著下次用 |
| **綁機** | payload 內含 `machine_fingerprint`，核銷時與本機重算值比對 | 拿到另一台機器上無效 |
| **短效** | payload 內含 `expires_at`，逾期核銷一律拒 | 預設 72 小時內要用掉 |

再加上**綁事件**：`tamper_event_id` 必須等於客戶端目前標記檔內的事件編號——**每一次鎖定都要原廠重新研判簽發，舊 token 不能沿用**。

### 過期或作廢後的重簽流程

token 過期、客戶弄丟、或「解鎖後檔案沒還原導致立刻再次鎖定」時：

1. 請客戶**重新回報當前畫面上的事件編號**（再次鎖定會產生**新的** `tamper_event_id`，舊的那張 token 對它無效）
2. 機器指紋通常不變（同一台機器），但仍請客戶一併確認
3. 依 §3 或 §4 重新簽一張，研判備註註明是重簽及原因

---

## 六、交付客戶

1. 透過既有安全管道（郵件／客戶入口）交付 `unlock.token` 檔
2. **同時提醒客戶三件事**：
   - 先把被改動的檔案還原（或重新部署原廠 image），**否則解鎖後會立刻再次鎖定**
   - 放檔位置與重啟方式見客戶手冊（`docs/user-manual/system-integrity-lockdown-guide.md`）
   - 🔴 **重啟必須用 `docker restart`，不可 `docker rm` 後重建容器**——重建可能改變機器指紋，token 隨即作廢
3. 指引客戶完成後回報結果；若失敗，請其提供啟動 log 的 `[INTEGRITY]` 區塊（失敗訊息會明確指出是四關中的哪一關）

---

## 七、客戶端核銷失敗的排查對照

客戶端核銷會印出明確的失敗原因（`[INTEGRITY] ⛔ 解鎖 token 核銷未通過`）。對照處置：

| 客戶端訊息要點 | 真因 | 處置 |
|---------------|------|------|
| 簽章驗證失敗 / 不是解鎖 token | 檔案被文字編輯器改動過、傳輸損毀，或客戶放錯檔（例如放了授權照） | 重新交付原始下載檔，勿用編輯器開啟 |
| 已於 … 過期 | 超過 `expires_at` | 重簽一張（§5） |
| 機器指紋與本機不符（訊息會印出本機指紋） | 指紋抄錯，或客戶重建了容器導致指紋改變 | 用訊息印出的**本機指紋**重簽 |
| 事件編號與本機目前的鎖定不符（訊息會印出本機編號） | 拿舊事件的 token 解新一次鎖定 | 用訊息印出的**本機事件編號**重簽 |
| 已經使用過，不可重複使用 | 同一張用第二次 | 重簽一張 |
| 鎖定紀錄檔已毀損，無法核對事件編號 | 客戶端標記檔損壞 | 一律不放行解鎖（防繞法）；需原廠協助人工處理 |
| 驗證通過但無法寫入已用紀錄（防重放） | 標記目錄不可寫（掛載或權限問題） | **token 尚未作廢**——請客戶修正 `/opt/guidant/pki` 掛載為可寫的持久 volume 後，同一張再試 |

> 四種失敗訊息刻意各不相同且含處置建議——含糊的訊息會讓現場把「指紋抄錯」誤判成「原廠簽錯了」，來回數輪查不出來。

---

## 附錄：名詞對照

| 名詞 | 意思 |
|------|------|
| **完整性驗證** | 比對產物檔案 SHA-256 與原廠簽章 manifest |
| **lockdown 殼** | 驗證失敗時取代正常服務的極簡 HTTP responder，任何請求都回 503 鎖定資訊，零業務功能、零寫入面 |
| **machine_fingerprint** | 本機識別碼（sha256 hex，64 字元），由 `/etc/machine-id` 推導 |
| **tamper_event_id** | 單次鎖定事件編號，`evt-` 開頭 |
| **unlock token** | 原廠簽發的一次性、綁機、短效解鎖憑證，檔名 `unlock.token` |
| **nonce** | token 內的一次性隨機值，客戶端記帳防重放 |
| **D9 回報** | 客戶端 best-effort 把 tamper 事件送回 License Center 留存（發不出不重試） |
