Files
FoFreeXit/docs/15-phase5-summary.md
T
adminandClaude Fable 5 f62cded997 Phase 5 Iteration 2: chữ ký số PKCS#7/PAdES + redact tỉa ký tự + lưu tối ưu
Engine — chữ ký số (sign.rs MỚI):
- generate_self_signed_id: Digital ID tự ký RSA-2048 + X.509 (rcgen), ghi PEM
- sign_pdf: đúng cơ chế Adobe — lopdf dựng sig dict (/SubFilter
  adbe.pkcs7.detached) + Widget field + AcroForm/SigFlags, ByteRange/Contents
  placeholder; serialize → vá ByteRange theo offset thật → SHA-256 trên 2 đoạn
  → CMS SignedData detached (crate cms, signed attrs messageDigest+contentType,
  RSA-SHA256) nhét DER vào Contents
- verify_signatures: parse ByteRange/Contents, băm lại, kiểm chữ ký RSA trên
  signed attrs + messageDigest khớp + phủ toàn bộ file → phát hiện GIẢ MẠO
- Bài học: cắt DER về đúng độ dài (bỏ padding hex '0'); bỏ qua /Contents của
  trang (tham chiếu), chỉ nhận /Contents <hex> của chữ ký

Engine — redact tỉa theo KÝ TỰ (redact.rs):
- Text chạm một phần: page_char_boxes xác định ký tự trong vùng, GIỮ các đoạn
  ngoài vùng, dựng lại đúng vị trí/baseline/màu (font nhúng gốc → cùng họ →
  mặc định). Guard: text xoay/không đọc được font → xoá nguyên (an toàn)
- qpdf::optimize_save: --object-streams=generate + nén, dọn object rác

Tauri + UI:
- Commands: sig_create_id, sig_sign, sig_verify, security_optimize,
  pick_any_file, pick_save_pem
- Thanh Bảo mật thêm: Tạo Digital ID, Ký số (dialog + tự kiểm tra sau khi ký),
  Kiểm tra chữ ký (modal bảng trạng thái + lý do), Lưu tối ưu

Test (7 mới → 62/62 engine xanh ngoài 2 fixture qpdf Linux):
- sign_then_verify_is_valid; tampering_after_signing_is_detected (phát hiện
  sửa sau khi ký); verify_reports_none_for_unsigned
- redact tỉa ký tự: cụm mật biến mất, phần cùng dòng ngoài vùng vẫn còn
- optimize_save không phình + giữ nội dung

Deps: rsa, sha2, cms, x509-cert, rcgen, pkcs8, der, spki, const-oid.
Docs: 15 mục 6 (iter 2 + giới hạn), 16 (S13–S19), README, roadmap (Phase 5
HOÀN TẤT), 14 (kế tiếp: Phase 6 Form).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HgnbPwSoRGNH7LHsDUmRpk
2026-07-15 09:14:51 +00:00

132 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 15 — Tổng kết Phase 5 (Bảo mật) · Iteration 1 + 2
> Trạng thái: **HOÀN TẤT Phase 5.** Iter 1: mã hoá + permissions, gỡ mật khẩu,
> redaction THẬT, xoá metadata. Iter 2: **chữ ký số PKCS#7/PAdES (ký + xác
> thực + phát hiện giả mạo), redaction tỉa theo KÝ TỰ, lưu tối ưu.**
> 62/62 test engine xanh (ngoài 2 fixture qpdf Linux). Chi tiết iter 2 ở mục 6.
## 1. Khảo sát Foxit (chuẩn nghiệm thu)
- **Protect › Password Protect**: user/owner password, AES-256, tick quyền
in/sửa/copy/chú thích. → ta làm dialog tương đương.
- **Protect › Mark for Redaction → Apply**: quét vùng → khối đen dashed →
Apply xoá nội dung THẬT (text không còn select/search được, ảnh bị đục),
vẽ khối đen. → ta làm đúng luồng 2 bước này.
- **Remove hidden information**: xoá metadata Author/Producer/XMP.
## 2. Đã làm
### Engine
- **`qpdf.rs`**: `Permissions {allow_print, allow_modify, allow_extract,
allow_annotate}` map sang cờ AES-256 của qpdf (`--print/--modify/--extract/
--annotate`); `encrypt_with_password_perms(...)` (API cũ giữ nguyên, uỷ
quyền với quyền đầy đủ); `decrypt_remove_password(input, password, output)`
(`qpdf --password=… --decrypt`) — password sai trả lỗi, không ghi file rác.
- **`redact.rs` (mới) — redaction THẬT, nghiêng về xoá thừa (an toàn)**:
- Text/path/shading/form GIAO vùng → xoá NGUYÊN object khỏi trang (v1 xoá
cả run bị chạm — sót chữ là lỗi bảo mật, xoá thừa thì không).
- Ảnh giao vùng → `get_raw_image` đọc pixel gốc, **bôi đen đúng phần giao
trong dữ liệu ảnh** rồi thay ảnh (nội dung gốc không còn trong file);
không đọc được pixel → xoá cả ảnh.
- Vẽ khối đen phủ mỗi vùng (dấu hiệu thị giác chuẩn redaction).
- **`meta.rs::strip_metadata`**: xoá `/Info` trong trailer (Author/Producer/
CreationDate…) + stream XMP `/Metadata` trong catalog — xoá cả THAM CHIẾU
lẫn OBJECT đích (kiểm bằng soi bytes thô của file kết quả). Qua lopdf;
trailer phi chuẩn được qpdf chuẩn hoá trước (fallback trong Tauri command).
### Tauri + UI (thanh "🔒 Bảo mật")
- Commands: `redact_apply` (nhiều trang, chain qua file tạm, trả số object đã
xử lý), `security_encrypt`, `security_decrypt`, `security_strip_metadata`.
- **Redact 2 bước như Foxit**: nút *⬛ Đánh dấu redact* (tool kéo-quét trên
trang, giữ tool để quét nhiều vùng; khối đen mờ viền đỏ dashed, bấm vào
khối để bỏ) → *Áp dụng redact (n)* → chọn nơi lưu → mở file kết quả.
- **Đặt mật khẩu**: dialog user/confirm/owner password + 4 checkbox quyền →
lưu bản mã hoá riêng (file đang mở giữ nguyên).
- **Gỡ mật khẩu**: nhập mật khẩu → chọn file khoá → lưu bản đã gỡ → mở luôn.
- **Xoá metadata**: 1 nút → lưu bản sạch → mở luôn. Không dùng prompt/alert.
## 3. Test (6 mới; tổng engine 58/58 ngoài qpdf-fixture)
- `redact_removes_text_content_for_real` — chuỗi mật biến mất khỏi
`extract_text`, chuỗi ngoài vùng còn nguyên, tâm vùng render ra ĐEN.
- `redact_blacks_out_image_pixels_for_real` — đọc lại RAW IMAGE từ file kết
quả: nửa bị redact đen trong chính dữ liệu ảnh, nửa kia vẫn màu gốc
(chứng minh không phải "vẽ đè").
- `encrypt_with_perms_round_trip_and_flags` — không mật khẩu bị chặn, đúng
user/owner mở được, `qpdf --show-encryption` xác nhận quyền in bị cấm.
- `decrypt_removes_password` + `decrypt_with_wrong_password_fails`.
- `strip_metadata_removes_info_and_xmp` — Producer/Author biến mất khỏi
BYTES của file, trailer sạch, file vẫn mở/đọc bình thường.
- `qpdf_safety` cũ: 5/7 xanh trên Linux CI; 2 test repair fixture
`corrupt-truncated.pdf` fail do **qpdf 11.9 Linux không dựng lại được
fixture này** (bản qpdf Windows của dự án xử lý được — khác biệt phiên bản
qpdf, không phải hồi quy code; cần xác nhận lại trên máy Windows).
## 4. Đối chiếu FINAL TARGET & RULE
- Redaction là "xoá thật + kiểm chứng được", không phải vẽ đè — test đọc lại
bytes/pixel/extract để chứng minh, đúng DoD roadmap.
- Không thoả hiệp dialog: mọi thao tác có UI chuyên nghiệp (modal + toolbar).
- Điểm CHƯA đạt 100% so với Foxit (ghi nhận, có kế hoạch):
- Redact theo VÙNG QUÉT, xoá cả run text bị chạm (Foxit tỉa theo ký tự).
Nâng cấp v2: cắt run tại ranh giới ký tự bằng char-box (đã có
`page_char_boxes` từ Phase 1) + tái tạo phần giữ lại như reflow.
- Chưa xoá "hidden information" dạng attachment/script/comment ẩn — gộp
vào Iteration 2.
## 5. (Đã thực hiện — xem mục 6.) Kế hoạch iteration 2 ban đầu
## 6. Iteration 2 — Chữ ký số + redact ký tự + lưu tối ưu (ĐÃ LÀM)
### 6.1 Chữ ký số PKCS#7/PAdES (`sign.rs`)
- **`generate_self_signed_id(cn, out.pem)`**: tạo Digital ID tự ký RSA-2048 +
X.509 (rcgen), ghi PEM bundle (cert + private key) — người dùng ký thử ngay
không cần CA; có PFX/PEM thật thì nạp thẳng.
- **`sign_pdf(input, id.pem, reason, name, output)`** đúng cơ chế Adobe:
1. Chuẩn hoá qua qpdf → lopdf dựng signature dict
(`/Type/Sig /SubFilter/adbe.pkcs7.detached`) với `/ByteRange` +
`/Contents` placeholder, field chữ ký (Widget vô hình) + AcroForm/SigFlags.
2. Serialize → định vị offset thật, vá `/ByteRange` (phần hex `<...>` của
Contents bị loại khỏi digest, 2 dấu `<` `>` vẫn nằm trong vùng ký).
3. SHA-256 trên 2 đoạn ByteRange → **CMS SignedData detached** (crate `cms`,
signed attrs gồm messageDigest + contentType, ký RSA-SHA256) → nhét DER
vào Contents.
- **`verify_signatures(input)`**: với mỗi chữ ký — parse ByteRange/Contents,
băm lại, kiểm (a) chữ ký RSA hợp lệ trên signed attrs, (b) messageDigest
khớp digest file, (c) chữ ký phủ TOÀN BỘ file. `is_valid()` = cả 3 đạt.
- Bài học: hex Contents có padding '0' → phải cắt DER về đúng độ dài (đọc
length trường DER) trước khi parse; `/Contents` của TRANG (tham chiếu
`N 0 R`) phải bỏ qua, chỉ nhận `/Contents <hex>` của chữ ký.
### 6.2 Redaction tỉa theo KÝ TỰ (`redact.rs`)
- Text object bị vùng redact chạm MỘT PHẦN: dùng `page_char_boxes` (Phase 1)
xác định ký tự nào nằm trong vùng, **giữ các đoạn ký tự ngoài vùng**, dựng
lại đúng vị trí/baseline/màu (font: nhúng gốc → cùng họ → mặc định).
- Guard an toàn: text XOAY/nghiêng (matrix b,c ≠ 0) hoặc không đọc được font →
xoá NGUYÊN object (thà thừa còn hơn sót). Ảnh vẫn bôi đen pixel như iter 1.
### 6.3 Lưu tối ưu (`qpdf::optimize_save`)
- `qpdf --object-streams=generate --compress-streams=y`: nén + dọn object mồ
côi (rác sau edit/redact — object đã tách khỏi trang nhưng còn trong file).
### 6.4 UI + Tauri
- Thanh Bảo mật thêm: **🪪 Tạo Digital ID**, **✍️ Ký số** (dialog tên/lý do/
chọn .pem → tự kiểm tra sau khi ký), **🔎 Kiểm tra chữ ký** (modal bảng
trạng thái hợp lệ/không + lý do), **📦 Lưu tối ưu**.
- Commands: `sig_create_id`, `sig_sign`, `sig_verify`, `security_optimize`,
`pick_any_file`, `pick_save_pem`.
### 6.5 Test iteration 2 (7 mới → tổng 62/62)
- `sign_then_verify_is_valid` — tạo ID → ký → xác thực hợp lệ, đúng tên người ký.
- `tampering_after_signing_is_detected` — lật 1 byte vùng đã ký → chữ ký KHÔNG
hợp lệ (chứng minh phát hiện giả mạo, cốt lõi của chữ ký số).
- `verify_reports_none_for_unsigned` — file chưa ký: 0 chữ ký.
- `redact_removes_text_content_for_real` (nâng): chuỗi mật biến mất nhưng
phần cùng dòng ngoài vùng **vẫn còn** (tỉa theo ký tự).
- `optimize_save_keeps_content_and_not_larger`.
### 6.6 Giới hạn còn lại (v-sau, không chặn dùng)
- Chữ ký tự ký (self-signed) → Acrobat báo "hợp lệ nhưng chưa tin cậy chuỗi
CA"; cần cài cert vào Trusted hoặc dùng CA thật. Cơ chế byte-range/CMS đã
chuẩn để Acrobat xác thực toán học.
- Chưa timestamp (PAdES-T) và LTV; chưa đọc PFX/PKCS#12 (mới PEM).
- Ký nhiều lần giữ chữ ký cũ (multi-sig incremental) chưa làm — mới ký lần đầu.
- Redact ký tự: đoạn XOAY vẫn xoá cả object (an toàn).