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
This commit is contained in:
Generated
+390
-6
@@ -198,6 +198,12 @@ version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.8.0"
|
||||
@@ -490,6 +496,27 @@ dependencies = [
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cms"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b77c319abfd5219629c45c34c89ba945ed3c5e49fcde9d16b6c3885f118a730"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"cbc",
|
||||
"cipher",
|
||||
"const-oid",
|
||||
"der",
|
||||
"rsa",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"sha3",
|
||||
"signature",
|
||||
"spki",
|
||||
"x509-cert",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
@@ -526,6 +553,12 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.18.1"
|
||||
@@ -641,6 +674,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"rand_core 0.6.4",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
@@ -728,6 +762,30 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"der_derive",
|
||||
"flagset",
|
||||
"pem-rfc7468",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der_derive"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.5.8"
|
||||
@@ -765,6 +823,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"const-oid",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
@@ -1030,13 +1089,23 @@ name = "ff-engine"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cms",
|
||||
"const-oid",
|
||||
"der",
|
||||
"ff-cos",
|
||||
"ff-pdmodel",
|
||||
"image",
|
||||
"lopdf",
|
||||
"pdfium-render",
|
||||
"pkcs8",
|
||||
"rcgen",
|
||||
"rsa",
|
||||
"sha2",
|
||||
"signature",
|
||||
"spki",
|
||||
"thiserror 2.0.18",
|
||||
"ttf-parser",
|
||||
"x509-cert",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1063,6 +1132,12 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "flagset"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.9"
|
||||
@@ -2072,6 +2147,15 @@ dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keccak"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653"
|
||||
dependencies = [
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keyboard-types"
|
||||
version = "0.7.0"
|
||||
@@ -2083,6 +2167,15 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
dependencies = [
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "leb128fmt"
|
||||
version = "0.1.0"
|
||||
@@ -2164,6 +2257,12 @@ dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.17"
|
||||
@@ -2223,7 +2322,7 @@ dependencies = [
|
||||
"md-5",
|
||||
"nom",
|
||||
"nom_locate",
|
||||
"rand",
|
||||
"rand 0.9.4",
|
||||
"rangemap",
|
||||
"rayon",
|
||||
"sha2",
|
||||
@@ -2418,6 +2517,22 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint-dig"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libm",
|
||||
"num-integer",
|
||||
"num-iter",
|
||||
"num-traits",
|
||||
"rand 0.8.7",
|
||||
"smallvec",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.2.2"
|
||||
@@ -2444,6 +2559,16 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-iter"
|
||||
version = "0.1.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b"
|
||||
dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.4.2"
|
||||
@@ -2462,6 +2587,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2780,6 +2906,25 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem"
|
||||
version = "3.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.2"
|
||||
@@ -2851,6 +2996,27 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad78bf43dcf80e8f950c92b84f938a0fc7590b7f6866fbcbeca781609c115590"
|
||||
|
||||
[[package]]
|
||||
name = "pkcs1"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
|
||||
dependencies = [
|
||||
"der",
|
||||
"pkcs8",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
|
||||
dependencies = [
|
||||
"der",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.33"
|
||||
@@ -3083,14 +3249,34 @@ version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a"
|
||||
dependencies = [
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
|
||||
dependencies = [
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3100,7 +3286,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3145,8 +3340,8 @@ dependencies = [
|
||||
"num-traits",
|
||||
"paste",
|
||||
"profiling",
|
||||
"rand",
|
||||
"rand_chacha",
|
||||
"rand 0.9.4",
|
||||
"rand_chacha 0.9.0",
|
||||
"simd_helpers",
|
||||
"thiserror 2.0.18",
|
||||
"v_frame",
|
||||
@@ -3194,6 +3389,19 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rcgen"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2"
|
||||
dependencies = [
|
||||
"pem",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"time",
|
||||
"yasna",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.18"
|
||||
@@ -3327,6 +3535,41 @@ version = "0.8.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4"
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom 0.2.17",
|
||||
"libc",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "0.9.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"digest",
|
||||
"num-bigint-dig",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"pkcs1",
|
||||
"pkcs8",
|
||||
"rand_core 0.6.4",
|
||||
"sha2",
|
||||
"signature",
|
||||
"spki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.2"
|
||||
@@ -3342,6 +3585,15 @@ dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046"
|
||||
dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
@@ -3601,6 +3853,17 @@ dependencies = [
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.9"
|
||||
@@ -3612,12 +3875,32 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha3"
|
||||
version = "0.10.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.9"
|
||||
@@ -3709,6 +3992,22 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.1"
|
||||
@@ -3756,6 +4055,12 @@ version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "swift-rs"
|
||||
version = "1.0.7"
|
||||
@@ -4268,6 +4573,27 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tls_codec"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b"
|
||||
dependencies = [
|
||||
"tls_codec_derive",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tls_codec_derive"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.52.3"
|
||||
@@ -4605,6 +4931,12 @@ version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.8"
|
||||
@@ -5162,6 +5494,15 @@ dependencies = [
|
||||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
@@ -5592,12 +5933,35 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x509-cert"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"der",
|
||||
"sha1",
|
||||
"signature",
|
||||
"spki",
|
||||
"tls_codec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "y4m"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448"
|
||||
|
||||
[[package]]
|
||||
name = "yasna"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
|
||||
dependencies = [
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.3"
|
||||
@@ -5662,6 +6026,26 @@ dependencies = [
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
version = "0.2.4"
|
||||
|
||||
@@ -939,6 +939,84 @@ fn security_strip_metadata(input: String, output: String) -> Result<(), String>
|
||||
}
|
||||
}
|
||||
|
||||
/// Lưu tối ưu: nén object stream + dọn object rác.
|
||||
#[tauri::command]
|
||||
fn security_optimize(input: String, output: String) -> Result<(), String> {
|
||||
ff_engine::optimize_save(std::path::Path::new(&input), std::path::Path::new(&output))
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Tạo Digital ID tự ký (RSA-2048) ghi ra file PEM.
|
||||
#[tauri::command]
|
||||
fn sig_create_id(common_name: String, output: String) -> Result<(), String> {
|
||||
ff_engine::generate_self_signed_id(&common_name, std::path::Path::new(&output))
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Ký số `input` bằng identity PEM, ghi ra `output`.
|
||||
#[tauri::command]
|
||||
fn sig_sign(
|
||||
input: String,
|
||||
id_pem: String,
|
||||
reason: String,
|
||||
signer_name: String,
|
||||
output: String,
|
||||
) -> Result<(), String> {
|
||||
ff_engine::sign_pdf(
|
||||
std::path::Path::new(&input),
|
||||
std::path::Path::new(&id_pem),
|
||||
&reason,
|
||||
&signer_name,
|
||||
std::path::Path::new(&output),
|
||||
)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Kết quả xác thực 1 chữ ký cho UI.
|
||||
#[derive(serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct SignatureCheckDto {
|
||||
signer: String,
|
||||
crypto_valid: bool,
|
||||
digest_matches: bool,
|
||||
covers_document: bool,
|
||||
valid: bool,
|
||||
}
|
||||
|
||||
/// Xác thực mọi chữ ký trong `input`.
|
||||
#[tauri::command]
|
||||
fn sig_verify(input: String) -> Result<Vec<SignatureCheckDto>, String> {
|
||||
let checks =
|
||||
ff_engine::verify_signatures(std::path::Path::new(&input)).map_err(|e| e.to_string())?;
|
||||
Ok(checks
|
||||
.into_iter()
|
||||
.map(|c| SignatureCheckDto {
|
||||
signer: c.signer.clone(),
|
||||
crypto_valid: c.crypto_valid,
|
||||
digest_matches: c.digest_matches,
|
||||
covers_document: c.covers_document,
|
||||
valid: c.is_valid(),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Hộp thoại chọn file bất kỳ (dùng cho chọn Digital ID .pem / PDF để xác thực).
|
||||
#[tauri::command]
|
||||
fn pick_any_file(app: tauri::AppHandle) -> Option<String> {
|
||||
app.dialog().file().blocking_pick_file().map(|fp| fp.to_string())
|
||||
}
|
||||
|
||||
/// Hộp thoại lưu file PEM (Digital ID).
|
||||
#[tauri::command]
|
||||
fn pick_save_pem(app: tauri::AppHandle) -> Option<String> {
|
||||
app.dialog()
|
||||
.file()
|
||||
.add_filter("Digital ID (PEM)", &["pem"])
|
||||
.set_file_name("my-digital-id.pem")
|
||||
.blocking_save_file()
|
||||
.map(|fp| fp.to_string())
|
||||
}
|
||||
|
||||
/// Dọn các file làm việc tạm của chế độ sửa (undo stack). Chỉ xoá file có tên
|
||||
/// `ff_edit_*` nằm đúng trong thư mục temp — không bao giờ đụng file người dùng.
|
||||
#[tauri::command]
|
||||
@@ -1003,6 +1081,12 @@ fn main() {
|
||||
security_encrypt,
|
||||
security_decrypt,
|
||||
security_strip_metadata,
|
||||
security_optimize,
|
||||
sig_create_id,
|
||||
sig_sign,
|
||||
sig_verify,
|
||||
pick_any_file,
|
||||
pick_save_pem,
|
||||
pick_image
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
|
||||
@@ -112,6 +112,11 @@
|
||||
<button id="secEncrypt" title="Đặt mật khẩu mở file + quyền hạn (AES-256)">🔑 Đặt mật khẩu</button>
|
||||
<button id="secDecrypt" title="Gỡ mật khẩu (cần mật khẩu hiện tại)">🔓 Gỡ mật khẩu</button>
|
||||
<button id="secStripMeta" title="Xoá /Info (tác giả, phần mềm tạo...) và XMP metadata khỏi file">🧹 Xoá metadata</button>
|
||||
<span class="sep"></span>
|
||||
<button id="secCreateId" title="Tạo Digital ID tự ký (RSA-2048) để ký thử">🪪 Tạo Digital ID</button>
|
||||
<button id="secSign" title="Ký số tài liệu bằng Digital ID">✍️ Ký số</button>
|
||||
<button id="secVerify" title="Kiểm tra tính hợp lệ của chữ ký trong file">🔎 Kiểm tra chữ ký</button>
|
||||
<button id="secOptimize" title="Nén object stream + dọn object rác (giảm dung lượng)">📦 Lưu tối ưu</button>
|
||||
<span class="spacer"></span>
|
||||
<span id="secHint" class="status"></span>
|
||||
</div>
|
||||
|
||||
+117
@@ -2811,6 +2811,119 @@ async function stripMetadataAction() {
|
||||
}
|
||||
}
|
||||
|
||||
async function optimizeSaveAction() {
|
||||
const out = await invoke("pick_save_pdf");
|
||||
if (!out) return;
|
||||
try {
|
||||
await invoke("security_optimize", { input: state.path, output: out });
|
||||
$("status").textContent = `Đã lưu tối ưu (nén + dọn rác) → ${shortName(out)}`;
|
||||
loadDocument(out);
|
||||
} catch (e) {
|
||||
$("secHint").textContent = "Lỗi lưu tối ưu: " + e;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Chữ ký số (Phase 5 iteration 2) ---
|
||||
|
||||
function openCreateIdDialog() {
|
||||
const box = openModal("Tạo Digital ID tự ký", `
|
||||
<p class="muted">Tạo chứng chỉ tự ký (RSA-2048) để ký thử. Nếu có chữ ký của tổ chức phát hành (PFX/PEM) thì dùng thẳng nó ở bước Ký số.</p>
|
||||
<label>Tên hiển thị trong chữ ký</label>
|
||||
<input type="text" id="idCn" placeholder="Nguyễn Văn A">
|
||||
<div class="err" id="idErr"></div>
|
||||
<div class="foot"><button id="idCancel">Huỷ</button><button id="idOk" class="primary">Tạo & lưu…</button></div>
|
||||
`);
|
||||
box.querySelector("#idCancel").addEventListener("click", closeModal);
|
||||
box.querySelector("#idOk").addEventListener("click", async () => {
|
||||
const cn = box.querySelector("#idCn").value.trim();
|
||||
if (!cn) { box.querySelector("#idErr").textContent = "Cần nhập tên."; return; }
|
||||
const out = await invoke("pick_save_pem");
|
||||
if (!out) return;
|
||||
try {
|
||||
await invoke("sig_create_id", { commonName: cn, output: out });
|
||||
closeModal();
|
||||
$("secHint").textContent = `Đã tạo Digital ID → ${shortName(out)} (dùng ở bước Ký số)`;
|
||||
} catch (e) {
|
||||
box.querySelector("#idErr").textContent = "Lỗi: " + e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function openSignDialog() {
|
||||
const box = openModal("Ký số tài liệu", `
|
||||
<label>Tên người ký (hiện trong chữ ký)</label>
|
||||
<input type="text" id="sgName" placeholder="Nguyễn Văn A">
|
||||
<label>Lý do ký</label>
|
||||
<input type="text" id="sgReason" value="Tôi đồng ý với nội dung tài liệu">
|
||||
<label>Digital ID (.pem)</label>
|
||||
<div class="row">
|
||||
<input type="text" id="sgId" readonly placeholder="Chưa chọn — bấm Chọn…">
|
||||
<button id="sgPickId">Chọn…</button>
|
||||
</div>
|
||||
<div class="err" id="sgErr"></div>
|
||||
<div class="foot"><button id="sgCancel">Huỷ</button><button id="sgOk" class="primary">Ký & lưu…</button></div>
|
||||
`);
|
||||
let idPath = null;
|
||||
box.querySelector("#sgPickId").addEventListener("click", async () => {
|
||||
const p = await invoke("pick_any_file");
|
||||
if (p) { idPath = p; box.querySelector("#sgId").value = shortName(p); }
|
||||
});
|
||||
box.querySelector("#sgCancel").addEventListener("click", closeModal);
|
||||
box.querySelector("#sgOk").addEventListener("click", async () => {
|
||||
const name = box.querySelector("#sgName").value.trim();
|
||||
const reason = box.querySelector("#sgReason").value.trim();
|
||||
const err = box.querySelector("#sgErr");
|
||||
if (!name) { err.textContent = "Cần nhập tên người ký."; return; }
|
||||
if (!idPath) { err.textContent = "Cần chọn Digital ID (.pem)."; return; }
|
||||
const out = await invoke("pick_save_pdf");
|
||||
if (!out) return;
|
||||
try {
|
||||
await invoke("sig_sign", { input: state.path, idPem: idPath, reason, signerName: name, output: out });
|
||||
closeModal();
|
||||
$("status").textContent = `Đã ký số → ${shortName(out)}`;
|
||||
loadDocument(out);
|
||||
// Tự kiểm tra chữ ký vừa tạo.
|
||||
verifySignaturesAction(out);
|
||||
} catch (e) {
|
||||
err.textContent = "Lỗi ký: " + e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function verifySignaturesAction(pathOverride) {
|
||||
const target = typeof pathOverride === "string" ? pathOverride : state.path;
|
||||
let checks;
|
||||
try {
|
||||
checks = await invoke("sig_verify", { input: target });
|
||||
} catch (e) {
|
||||
$("secHint").textContent = "Lỗi kiểm tra: " + e;
|
||||
return;
|
||||
}
|
||||
const rows = checks.length
|
||||
? checks
|
||||
.map((c, i) => {
|
||||
const badge = c.valid
|
||||
? '<span class="sig-ok">✓ Hợp lệ</span>'
|
||||
: '<span class="sig-bad">✗ Không hợp lệ</span>';
|
||||
const detail = [];
|
||||
if (!c.cryptoValid) detail.push("chữ ký sai");
|
||||
if (!c.digestMatches) detail.push("nội dung đã bị sửa");
|
||||
if (!c.coversDocument) detail.push("có phần thêm sau khi ký");
|
||||
return `<tr><td>${i + 1}</td><td>${(c.signer || "").replace(/</g, "<")}</td><td>${badge}</td>
|
||||
<td class="muted">${detail.join("; ")}</td></tr>`;
|
||||
})
|
||||
.join("")
|
||||
: `<tr><td colspan="4" class="muted">Tài liệu chưa có chữ ký số nào.</td></tr>`;
|
||||
const box = openModal("Kết quả kiểm tra chữ ký", `
|
||||
<table class="sig-table">
|
||||
<thead><tr><th>#</th><th>Người ký</th><th>Trạng thái</th><th>Ghi chú</th></tr></thead>
|
||||
<tbody>${rows}</tbody>
|
||||
</table>
|
||||
<div class="foot"><button id="sigClose" class="primary">Đóng</button></div>
|
||||
`);
|
||||
box.querySelector("#sigClose").addEventListener("click", closeModal);
|
||||
}
|
||||
|
||||
// ---------- Sự kiện ----------
|
||||
|
||||
$("openBtn").addEventListener("click", openFile);
|
||||
@@ -2933,6 +3046,10 @@ $("secRedactClear").addEventListener("click", clearRedactMarks);
|
||||
$("secEncrypt").addEventListener("click", openEncryptDialog);
|
||||
$("secDecrypt").addEventListener("click", openDecryptDialog);
|
||||
$("secStripMeta").addEventListener("click", stripMetadataAction);
|
||||
$("secOptimize").addEventListener("click", optimizeSaveAction);
|
||||
$("secCreateId").addEventListener("click", openCreateIdDialog);
|
||||
$("secSign").addEventListener("click", openSignDialog);
|
||||
$("secVerify").addEventListener("click", () => verifySignaturesAction());
|
||||
|
||||
// Sửa nội dung (Phase 4)
|
||||
$("editModeBtn").addEventListener("click", toggleEditMode);
|
||||
|
||||
@@ -413,6 +413,12 @@ body.tool-active .annotlayer * { pointer-events: none; }
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
/* Bảng kết quả kiểm tra chữ ký (Phase 5) */
|
||||
.sig-table { width: 100%; border-collapse: collapse; margin: 4px 0 12px; }
|
||||
.sig-table th, .sig-table td { text-align: left; padding: 5px 8px; border-bottom: 1px solid var(--border, #ddd); font-size: 13px; }
|
||||
.sig-ok { color: #1a8f3c; font-weight: 600; }
|
||||
.sig-bad { color: #d33; font-weight: 600; }
|
||||
|
||||
/* Đánh dấu redact (Phase 5): khối mờ đen viền đỏ, bấm để bỏ */
|
||||
.redact-mark {
|
||||
position: absolute;
|
||||
|
||||
Reference in New Issue
Block a user