# MODPKICA For Java **Repository Path**: wxgshuju/modpkicaforjava ## Basic Information - **Project Name**: MODPKICA For Java - **Description**: MODPKICA是离线企业CA系统,完整支持RSA/ECC与国密SM2‑SM3算法。遵循RFC3161、RFC6960,适配GB/T 35275‑2017、GB/T 38540‑2020、GM/T 0031‑2025国密签章标准,可完成根/中间/终端证书签发、CRL吊销、OCSP状态查询、时间戳服务。内置WebAdmin可视化管管理面板,支持EXE代码签名、PDF国密电子签章与LTV长验。 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: https://gitee.com/wxgshuju/modpkicaforjava - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2026-09-12 - **Last Updated**: 2026-09-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # MODPKICA Java 复刻版 v1.23(BC 1.85 离线) > 使用 **Java + BouncyCastle 1.85** 离线复刻 Go 版 MODPKICA 全套 PKI 功能:X.509 证书签发、CRL 吊销列表、OCSP 在线状态、RFC3161 / Authenticode 时间戳、RFC 6962 CT 日志服务(SCT 服务管理)、WebAdmin 浏览器管理面板。 > > - **技术栈**:纯 `javac` 编译,无 Maven / Gradle,无网络依赖,离线可开发 > - **依赖**:`BC185/` 目录下 BouncyCastle 1.85 全家桶(bcprov / bcpkix / bcutil / bcmail)+ `lib/`(pdfbox-app-3.0.4.jar) > - **平台**:Windows(build.cmd)/ Linux(build.sh) > - **作者**:@魔帝本尊(QQ:2829969554) --- ## 目录 - [界面截图](#界面截图) - [功能特性](#功能特性) - [项目结构](#项目结构) - [环境要求](#环境要求) - [一键编译](#一键编译) - [运行方式](#运行方式) - [命令行用法(7 个主程序)](#命令行用法7-个主程序) - [WebAdmin 管理面板](#webadmin-管理面板) - [PKI 目录与文件说明](#pki-目录与文件说明) - [证书类型与命名规则](#证书类型与命名规则) - [配置文件 CONFIG.txt](#配置文件-configtxt) - [登记表 CERTS.txt](#登记表-cerstxt) - [操作日志 logs.log](#操作日志-logslog) - [常见问题 FAQ](#常见问题-faq) - [更新记录](#更新记录) --- ## 界面截图 ### 仪表盘(证书链树 + 服务状态) 根 R → 中间 CA C → 终端 E 的证书链树,节点悬停显示证书参数;顶部一键刷新全部 CRL / 签发全部 OCSP / 签发全部时间戳;底部最近日志。 ![仪表盘](a.png) ### 证书库(CERTS.txt 列表) 序列号 / 类型 / 状态 / 原因 / 时间 / 颁发者 ID / 算法 / 操作,支持筛选与刷新;类型中文显示(R=根证书 / C=CA证书 / E=终端证书),状态三色(V 正常绿 / R 已吊销深红 / N 未知橙);序列号与颁发者序列号悬停显示证书简要信息;每行含详情 / 验证 / 操作三个按钮(验证弹窗按状态显示绿/红/橙标题)。 ![证书库](b.png) ### 签发证书(参数化表单) 证书 DN(key=value 或 OID)、证书类型、密钥算法(RSA / ECC / SM2)、密钥位数(RSA 可编辑 1024~8192,ECC 只读 256/384/521,SM2 只读 256)、哈希算法、KeyUsage(下拉 + 悬浮多选组合)、EKU(编号/OID 悬浮选择器 35 项)、有效期(年数或起止日期,秒级选择)。 ![签发证书](c.png) ### 证书操作(吊销 / 续签 / 密钥更换) 证书库每行「操作」按钮弹出窗口,默认「吊销证书」分组:序列号可输入或下拉选择,显示证书简要信息(类型 / 序列号 / DN / 密钥对 / 哈希 / 长度 / OCSP 状态 / CRL 状态),选择吊销理由后确认吊销,或「恢复证书」将状态 R→V、原因置 0 并同步生成 CRL;另有「续签证书」(选择生效 / 失效时间重签)与「密钥更换」(按原算法生成新密钥对测试后重签,SKI 随新公钥更新)分组。 ![证书操作](h.png) ### 证书链详情(悬停悬浮窗) 证书链树节点悬停弹出详情:证书类型(含策略识别)、序列号、DN、密钥对 / 哈希 / 密钥长度、OCSP 状态、CRL 状态。 ![证书链详情](e.png) ### 调试专区(模拟 OCSP / 时间戳请求) 按标准协议构造请求并 POST 本机 MAIN,日志逐步展示:模拟 OCSP 请求(RFC 6960,CertID 基于真正颁发者证书 + Nonce,哈希算法可选 SHA1/SHA256/SHA384/SHA512/SM3,验证响应签名与 Nonce 回显);模拟时间戳请求(RFC 3161,随机 32 字节 → 按所选摘要算法计算 messageImprint → TimeStampReq(Nonce + certReq)→ 解析 TimeStampResp 并验证 token 签名)。 ![调试专区](g.png) ### 签名专区(选择夹:微软代码签名 / PDF 文档签名) **选择夹布局**:签名专区改为上下两个独立页签(tab)——「微软代码签名」与「PDF 文档签名」各占整页,空间更大、互不干扰;全局字体 15px,控件间距与列表行高同步放大。 **微软代码签名(Authenticode)**:**同样支持批量选择与拖拽添加**——把 exe / dll / sys / cab / cat / msi 文件拖入虚线拖拽区或点击「选择文件」多选;待签名列表逐行显示文件名 / 大小 / 状态(待处理 / 签名中 / 成功 / 失败),行内可单独签名、验证、移除,成功后行内下载;顶部「批量签名」「批量验证」「清空」一次处理全部。签名支持 RSA / ECC / SM2(摘要分别 SHA256 / SHA256 / SM3),证书表按 Microsoft PE/COFF 规范嵌入。**时间戳类型**下拉可选 **RFC3161**(标准 TimeStampToken)或 **Authenticode**(老式 PKCS#7);**时间戳请求地址**可编辑下拉留空 = 本机 MAIN,或指向 DigiCert / Symantec / Comodo / Certum 等外部 TSA。 **PDF 文档签名**:**支持批量选择(多选文件)与拖拽添加**——把 PDF 文件直接拖入虚线拖拽区,或点击「选择 PDF 文件」多选;待签名列表逐行显示文件名 / 大小 / 状态(待处理 / 签名中 / 成功 / 失败),每行可单独签名、验证、移除,成功后可下载;顶部「批量签名」「批量验证」「清空」一次处理全部。证书(带文档签名 EKU,微软 OID `1.3.6.1.4.1.311.10.3.12` 或通用 OID `1.3.6.1.5.5.7.3.36` 任一即可;**证书留空 = 纯文档时间戳,不签名**)。**签章类型**下拉四种:Adobe 标准文档签名(`adbe.pkcs7.detached`,RSA/ECC/SM2 均可,可选签章图片生成可见签章;**选中证书时时间戳按 CAdES-T / PAdES-T 标准嵌入签名内部(SignerInfo 的 unsignedAttrs 属性 id-aa-timeStampToken,messageImprint = SHA-256(签名值)),DSS/LTV 数据(证书链 + OCSP 响应 + CRL)与签名同一次保存、被签名覆盖,签名后零增量**;不选证书时生成独立 DocTimeStamp(ETSI.RFC3161)纯文档时间戳,同样带 DSS/LTV);国密 GB/T 35275-2017(`GM.sm2cms.detached`,仅 SM2 证书 + 签章图;同样支持 CAdES-T 签名时间戳(SM3 摘要 + unsignedAttrs)与 DSS/LTV 同保存);国密 GB/T 38540-2020(`GM.sm2seal` SES_Signature,仅 SM2 证书 + 签章图;按标准 7.1.6 支持嵌入时间戳——SES_Signature 可选第 5 字段 timeStamp(对签名值的时间戳,符合 GB/T 20520= RFC3161,DER 编码,SM3 messageImprint),并支持 DSS/LTV 同保存);国密 GM/T 0031-2025(`GM.sm2seal`,仅 SM2 证书 + 签章图,数据结构与 38540 一致)。验证支持三种类型:CMS 验签 / 文档摘要比对 / 证书链(Adobe 与 35275),SES_Signature 专用解析(dataHash 比对 + SM2 验签,自动判别 38540 四/五字段、0031 两字段;38540 时间戳字段 timeStamp 解析:TSA/时间/算法/绑定签名值(SM3)+ TSA 证书验签);Adobe 模式逐签名字段验证(数字签名含签名时间戳 CAdES-T unsignedAttrs 解析:TSA/时间/算法/绑定签名值,哈希算法自适应 SHA-256/SM3;DocTimeStamp 走 TimeStampToken 专用解析:messageImprint 比对 + TSA 证书验签),输出含 LTV 状态(DSS Certs/OCSPs/CRLs 数量)。 ![签名专区选择夹](f.png) ### 签名专区(PDF 国密 GM/T 0031-2025 签章) PDF 文档签名分组选择「国密 GM/T 0031-2025」签章类型(下拉展开示意),选择 SM2 证书与签章图片后签名,结果区展示签章类型 / 签章字节 / 时间戳地址 / 输入文件 / 证书 / 序列号 / 算法摘要 / 输出字节,并提供下载签名后文件链接。 ![签名专区 GM0031](i.png) ### 签名专区(签章图片素材示例) 国密 PDF 电子签章(35275 / 38540 / 0031)需选择签章图片(png/jpg/bmp/gif),本图为内置示例签章 `stamp.png`(红色圆形电子签章:MODPKICA / 电子签章)。 ![签章素材](stamp.png) ### SCT 服务管理(CT 日志密钥管理) 「CT日志密钥管理」与「CT请求日志」选择夹布局;密钥管理:描述输入 + 生成 ECDSA / RSA / SM2 密钥对三按钮 + 刷新列表;密钥列表逐行显示 序号 / 描述 / 类型 / 日志公钥 ID / 状态(可用绿 / 不可用红)/ 创建时间 / 操作(删除 / 启用 / 暂停),密钥写入 `PKI/CTLOG/modpkica_ct_list.json`(v3 风格,`type` 字段区分 ECDSA/RSA/SM2,缺省 ECDSA)。 ![SCT服务管理](j.png) ### 证书库(SCT 列表验签弹窗) 证书库「验证」按钮弹窗的 SCT列表 区块:逐条显示 日志版本 / 日志名称 / 日志标识符 / 时间戳 / 签名类型 / 签名 / 状态(签名有效 / 签名无效 / 未知日志,无法验证)——SM2 证书的 SCT 由 SM2 日志密钥签名(SM2-SM3),状态显示"签名有效"。 ![SCT验签弹窗](k.png) ### 操作日志(logs.log 可视化) 时间范围筛选(最近 1/6/12/24 小时、全部)、行数设置、自动刷新(3 秒)、手动刷新;每条日志含时间、时间戳、操作、命令、结果。 ![操作日志](d.png) --- ## 功能特性 ### 1. MAIN —— 网络服务主程序 - **OCSP 在线证书状态协议**:`/OCSP`、`/ocsp`(POST),按请求证书序列号查 CERTS.txt 返回正常 / 已吊销 / 未知状态;支持 Nonce、按请求摘要算法自适应(SHA1/SHA256/SHA384/SHA512,SM2 固定 SM3,无摘要默认 SHA1);未知序列号返回无签名 unauthorized 响应;用 `序列号_ocsp.crt/.key` 签署响应;SM2 分支吊销响应按 RFC 6960 标准编码(`revoked [1] IMPLICIT RevokedInfo`),openssl / BC 均可正常解析 - **CRL 吊销列表下载**:`/CRL/序列号.crl`、`/CRL/序列号.der.crl` - **证书下载**:`/CRT/序列号.crt` - **RFC3161 时间戳**:`/rfc3161`(文档签名专用,SHA1 / SHA256 自适应) - **微软 Authenticode 时间戳**:`/timestamp`(SHA256)、`/timestamp/sha1`(SHA1);请求体为 base64(容忍 CRLF / 末尾 NUL 等杂字符),响应为 PKCS#7 分离签名(eContent + signedAttrs 含 id-data 属性) - **自适应根 TSA**:SHA1 → 随机 `rsa_sha1` / `ecc_sha1`;SHA256 → 随机 `rsa_sha256` / `ecc_sha256`;SM3/SM2 → `sm2_sm3` - **CT 日志服务(RFC 6962)**:`/ct/add`(仅 POST),请求体为「PEM 预证书 + PEM 颁发者证书」,按 RFC 6962 §3.1 构造 precert_entry(issuer_key_hash + 去 poison TBS)→ 加载 `PKI/CTLOG/modpkica_ct_list.json` 中 usable 密钥签名 → 返回二进制 SCTList TLS 编码;**类型匹配选组**:SM2 预证书只选 usable 的 SM2 密钥,RSA/ECDSA 预证书只选 RSA+ECDSA 密钥,随机 ≤3 组(仅一组就签一组);SCT 枚举 SM2=(7,4)(国密 CT 实现枚举,对齐零信 sm2ct.cn),ECDSA=(4,3),RSA=(4,1);每密钥一行追加 `PKI/CTLOG/ctlogs.txt`(`SCT待签名摘要||时间戳||终端SKI||终端AKI||日志公钥ID`) - **静态网页**:`/` 提供 `PKI/WebPublic` 下文件 - **启动时自动刷新全部 CRL** - **内嵌 WebAdmin 管理面板**(管理端口见下) ### 2. Admin —— 管理控制台(命令行) `init` 初始化环境、`makeroot` 签发根证书、`list` 列出证书、`signcert` 签发证书、`revoke` 吊销证书、`signCRL` 刷新 CRL、`initOCSP` 签发全部 OCSP 证书、`initTIMESTAMP` 签发全部时间戳证书、`verifyCERT` 证书验证。 ### 3. MakeRoot —— 签发根证书 生成 `ROOT/序列号.crt`、`ROOT/序列号.key`,同步 `WebPublic/CRT/序列号.crt`,登记 CERTS.txt(类型 R)。 ### 4. MakeCert —— 签发下级证书 支持签发中间 CA(类型 C)、终端证书(类型 E);颁发者证书类型自动从 ROOT / CA 目录加载密钥对;支持 RSA / ECC / SM2、SHA1 / SHA256 / SHA384 / SHA512 / SM3;密钥用法、增强型密钥用法(EKU 编号或 OID)、证书策略、SAN、有效期自定义。**SCT 预证书流程**:签发服务器 / 客户端证书(EKU 含服务器/客户端认证)时,先签发含 poison 扩展(RFC 6962)的 SCT 预证书,POST 本机 `/ct/add` 获取 SCTList,嵌入 SCT 扩展(OID `1.3.6.1.4.1.11129.2.4.2`)二次签名生成最终证书(已移除自创 2.4.5 扩展)。 ### 5. RootGetCrl —— 签发吊销列表 遍历 CERTS.txt 中全部 R / C 证书,为每个颁发者生成独立 CRL(`WebPublic/CRL/序列号.crl`),哈希算法沿用颁发者证书(如 RSA/sha256、RSA/sha512)。 ### 6. CertVerify —— 证书验证 输入证书文件或序列号,打印证书全字段(版本、序列号、签名算法、DN、有效期、公钥、SKI/AKI、EKU、SAN、AIA、CRL 分发点、KeyUsage、基本约束、证书策略、扩展、SCT)、签名验证、证书链、CRL + OCSP 吊销状态、各类哈希。**SCT 列表逐条验签**(RFC 6962):按 log_id 匹配日志公钥(本地 CTLOG 密钥库 + 权威 log_list.json),构造 precert_entry / x509_entry 待签名数据重算摘要,ECDSA(DER + 裸 r‖s 兼容)/ RSA / SM2(枚举 7,4 与 7,7 均支持)验签,输出 日志名称 / 日志标识符 / 时间戳 / 签名类型 / 状态(签名有效 / 签名无效 / 未知日志,无法验证)。 ### 7. Auto —— 自动化命令 `init` 初始化环境、`revoke 序列号 原因` 吊销证书、`newcert 序列号 类型 状态 原因 时间 颁发者ID 算法` 手工登记。 ### WebAdmin —— 浏览器管理面板 深色主题管理后台(15 个页面,含「SCT服务管理」),详见下文。 --- ## 项目结构 ``` modpkica4JAVA/ ├── BC185/ # BouncyCastle 1.85 离线 jar 全家桶 ├── lib/ # pdfbox-app-3.0.4.jar(PDF 签名/验证) │ ├── bcprov-jdk18on-1.85.jar # 加密 Provider │ ├── bcpkix-jdk18on-1.85.jar # 证书 / CMS / TSP │ ├── bcutil-jdk18on-1.85.jar # ASN.1 工具 │ └── bcmail-jdk18on-1.85.jar # 邮件 / SMIME ├── src/modpkica/ # 全部源码(21 个类) ├── out/ # 编译输出(javac -d out) ├── build.cmd # Windows 一键编译脚本 ├── build.sh # Linux 一键编译脚本 ├── PKI/ # 运行时数据目录(MODPKICA_BASE 指向项目根时) │ ├── ROOT/ # 根证书(类型 R) 序列号.crt / 序列号.key │ ├── CA/ # 中间证书(类型 C) 序列号.crt / 序列号.key │ ├── CERT/ # 终端证书 PEM(类型 E) 序列号.crt │ ├── KEY/ # 终端证书私钥(类型 E) 序列号.key │ ├── OCSP/ # OCSP 签名证书 序列号_ocsp.crt / .key │ ├── TIMSTAMP/ # TSA 时间戳证书(类型_哈希算法.crt,如 rsa_sha256.crt) │ ├── WebPublic/ │ │ ├── CRT/ # 全部证书对外下载目录(序列号.crt) │ │ └── CRL/ # 全部 CRL 对外下载目录(序列号.crl) │ ├── CERTS.txt # 证书登记表 │ ├── CONFIG.txt # 系统配置 │ └── logs.log # 操作日志(只追加) ├── a.png ~ k.png # WebAdmin 界面截图(本文档配图) ├── stamp.png # PDF 国密签章示例图片 ├── README.md # 本文件 └── 开发助手_源码分析.pdf # 源码结构 / 运行流程 / 常量函数速查 ``` ### 源码类一览(src/modpkica/) | 类 | 职责 | |---|---| | `MAIN` | 主程序 1:HTTP 服务(OCSP / CRL / 时间戳 / 静态网页)+ 内嵌 WebAdmin | | `Admin` | 主程序 2:管理控制台(doInit / doMakeRoot / doSignCert / doRevoke / doSignCrl / doInitOcsp / doInitTimestamp / doVerifyCert / doList) | | `MakeRoot` | 主程序 3:签发根证书 | | `MakeCert` | 主程序 4:签发下级证书(CA / 终端) | | `RootGetCrl` | 主程序 5:生成全部 CRL | | `CertVerify` | 主程序 6:证书验证(详情 / 签名 / 链 / 吊销 / 哈希) | | `Auto` | 主程序 7:自动化(init / revoke / newcert) | | `WebAdmin` | 浏览器管理面板(8080 管理端口,15 页面,32 个 API) | | `Bc` | BC Provider 注册 | | `PemIO` | PEM 证书 / 私钥读写、密钥类型探测 | | `CertUtil` | 证书构建工具(SKI、扩展组装、私钥读取等) | | `CrlUtil` | CRL 生成 / DER→PEM | | `OcspUtil` | OCSP 请求解析 / 响应生成(含 SM2、Nonce、无签名响应) | | `TimestampUtil` | RFC3161 与 Authenticode 时间戳(含 SM2→SM3) | | `Sm2Util` | 国密 SM2 签名 / 验签 / SM3 / ZA 计算 | | `SignTool` | EXE / PDF 签名与验证(Authenticode 手工 ASN.1、RFC3161/Authenticode 时间戳、Adobe 与国密 PDF 电子签章、SES_Signature 验证) | | `CtLog` | CT 日志密钥库 CRUD(ECDSA/RSA/SM2 多算法)、RFC 6962 预证书 SCT 签名、SCTList 编码、类型匹配选组(≤3 组)、`isSm2Precert` 判断 | | `CertsDB` | CERTS.txt 登记表加载 / 查询 / 刷新 | | `Config` | CONFIG.txt 配置加载 | | `MLog` | 追加式操作日志(时间 / 时间戳 / 操作 / 命令 / 结果) | | `AppPaths` | 工作目录定位(环境变量 MODPKICA_BASE) | | `AppEnv` | 可执行程序清单 | | `X509JsonConverter` | X.509 证书 → JSON 结构化(证书库详情展示用) | --- ## 环境要求 | 项目 | 要求 | |---|---| | JDK | 17+(已装 JDK 17/21 均可),`javac` / `java` 在 PATH 中 | | 依赖 | 无需联网,`BC185/`(BC 1.85 全家桶)+ `lib/`(pdfbox-app-3.0.4.jar) | | 系统 | Windows 10/11 或 Linux(脚本分别提供 .cmd / .sh) | --- ## 一键编译 ### Windows 双击 `build.cmd`,或在 cmd 中执行: ```bat build.cmd ``` 脚本流程:检查 javac → 清理 `out/` → `javac -encoding UTF-8 -cp "BC185\*;lib\*" -d out src\modpkica\*.java` → 校验 8 个 class(7 个主程序 + WebAdmin)→ 编译成功才打印运行命令提示。 **编号参数(编译后自动运行):** ```bat build.cmd [1-8] ``` `build.cmd` 后跟 1~8 的编号,编译**成功后**自动执行对应编号的命令;编译**失败**则跳过,不运行任何命令。编号非法(非 1-8)会在编译前直接报错退出。 | 编号 | 自动运行的命令(等价于手敲 java 命令) | |---|---| | `1` | `java "-Dfile.encoding=UTF-8" -cp "out;BC185\*;lib\*" modpkica.MAIN`(MAIN 服务 + WebAdmin 面板,默认 8080 端口) | | `2` | `java "-Dfile.encoding=UTF-8" -cp "out;BC185\*;lib\*" modpkica.WebAdmin`(只开管理面板) | | `3` | `java "-Dfile.encoding=UTF-8" -cp "out;BC185\*;lib\*" modpkica.Admin`(交互式管理控制台) | | `4` | `java ... modpkica.MakeRoot "CN=Root,O=Org,C=CN" 2048 sha256 RSA`(签发根证书**示例参数**) | | `5` | `java ... modpkica.MakeCert "CN=www.test.com" 2048 sha256 1 1 0 1 "www.test.com" null null root RSA`(签发证书**示例参数**) | | `6` | `java ... modpkica.RootGetCrl`(刷新全部 CRL) | | `7` | `java ... modpkica.CertVerify cert.crt`(验证**示例文件** cert.crt) | | `8` | `java ... modpkica.Auto revoke 1234567890 1`(吊销**示例序列号**) | > **注意**:编号 4/5/7/8 自动运行的是脚本内置的**固定示例参数**,不会透传额外参数(如 `build.cmd 8 revoke xxx 5` 中的 `revoke xxx 5` 会被忽略)。实际业务参数请改脚本 `:run_4`~`:run_8` 段落,或直接手敲完整 `java` 命令。 ### Linux ```bash chmod +x build.sh ./build.sh # 只编译 ./build.sh 1 # 编译成功后自动运行 MAIN(编号 1-8,与 build.cmd 完全一致) ``` > 编译成功后所有类在 `out/` 目录。 --- ## 运行方式 所有主程序统一入口格式: ```bat set MODPKICA_BASE=你的PKI根目录 java "-Dfile.encoding=UTF-8" -cp "out;BC185\*;lib\*" modpkica.主程序名 [参数...] ``` > **重要**:Windows 下必须加 `-Dfile.encoding=UTF-8`,否则中文日志 / 中文 DN 乱码(GBK 问题)。 > `MODPKICA_BASE` 缺省时取当前工作目录。示例环境为 `C:\Users\28299\Desktop\modpkica4JAVA`(PKI 目录直接位于其下)。 **运行 MAIN(推荐,WebAdmin 面板随服务自动启动):** ```bat set MODPKICA_BASE=C:\Users\28299\Desktop\modpkica4JAVA java "-Dfile.encoding=UTF-8" -cp "out;BC185\*;lib\*" modpkica.MAIN ``` - WEB 服务端口:`PKI/CONFIG.txt` 的 `WEBPORT`(默认 80) - WebAdmin 管理端口:默认 8080;可用系统属性改:`-Dmodpkica.admin.port=8082` - 启动后浏览器访问 `http://localhost:8080`(管理面板),`http://localhost:80`(WEB 服务) **只运行 WebAdmin(不启动 MAIN 服务):** ```bat java "-Dfile.encoding=UTF-8" -cp "out;BC185\*;lib\*" modpkica.WebAdmin ``` --- ## 命令行用法(7 个主程序) ### Admin(管理控制台) ```bat java "-Dfile.encoding=UTF-8" -cp "out;BC185\*;lib\*" modpkica.Admin ``` 交互命令一览: | 命令 | 说明 | |---|---| | `init` | 初始化环境(清空证书目录 + 重建 CERTS.txt) | | `makeroot 证书DN 密钥位数 哈希算法 密钥算法` | 签发根证书,如 `makeroot "CN=Root,O=My Org,C=CN" 2048 sha256 RSA` | | `list` | 列出全部登记证书 | | `signcert 证书DN 密钥位数 哈希算法 证书类型 有效期年数 SAN类型 SAN内容 颁发者序列号 密钥算法` | 签发下级证书 | | `revoke 序列号 原因编号` | 吊销证书(原因 0-9) | | `signCRL` | 刷新全部 CRL | | `initOCSP 密钥算法 [序列号]` | 为全部(或指定)R/C 证书签发 OCSP 签名证书 | | `initTIMESTAMP 哈希算法` | 签发时间戳证书(rsa/ecc/sm2 × sha1/sha256/sm3) | | `verifyCERT 证书文件或序列号` | 证书验证 | ### MakeRoot ```bat java ... modpkica.MakeRoot "CN=Root,O=My Org,C=CN" 2048 sha256 RSA ``` ### MakeCert ```bat java ... modpkica.MakeCert "CN=www.test.com" 2048 sha256 1 1 0 1 "www.test.com" null null root RSA ``` ### RootGetCrl ```bat java ... modpkica.RootGetCrl ``` ### CertVerify ```bat java ... modpkica.CertVerify cert.crt # 按文件 java ... modpkica.CertVerify 113521bfd # 按序列号(自动去 WebPublic/CRT 查找) ``` ### Auto ```bat java ... modpkica.Auto init java ... modpkica.Auto revoke 113521bfd 5 java ... modpkica.Auto newcert 序列号 E V 0 null 13fbe86586d1bca RSA ``` --- ## WebAdmin 管理面板 启动 MAIN 或 WebAdmin 后,浏览器访问管理端口(默认 `http://localhost:8080`)。 ### 页面(15 个) | 页面 | 功能 | |---|---| | 仪表盘 | 服务状态、证书链树(R→C→E,悬停详情、R 展开 / C 收起管理下级)、一键刷新 CRL / 签发 OCSP / 签发时间戳、最近日志 | | 证书库 | CERTS.txt 列表(类型中文:R=根证书 / C=CA证书 / E=终端证书;状态三色:V 正常绿 / R 已吊销深红 / N 未知橙;筛选、刷新)、序列号与颁发者序列号悬停悬浮窗、详情按钮(JSON 结构化证书信息)、验证按钮(弹窗三态:正常/已吊销/未知 + SCT 列表逐条验签)、操作按钮(吊销证书 / 续签证书 / 密钥更换分组) | | 签发根证书 | 根证书参数表单 | | 签发证书 | 完整参数表单(DN / 类型 / 密钥算法 / 密钥位数联动 / 哈希 / KeyUsage / EKU / 有效期 / SAN) | | 吊销证书 | 按序列号 + 原因吊销 | | CRL 刷新 | 重新生成全部 CRL | | OCSP 签发 | 为全部 R/C 证书签发 OCSP 签名证书 | | 时间戳签发 | 签发时间戳证书 | | 证书验证 | 输入证书文件或序列号验证(证书策略识别含 SSL/代码签名/文档签名/TSA 等 OID;SCT 列表逐条验签,日志公钥源:本地 CTLOG 密钥库 + 权威 log_list.json) | | 初始化环境 | 清空重建(不会执行重置类脚本,由 WEB 面板提供) | | 系统配置 | 可视化读写 CONFIG.txt(WEBPORT / SUPORT / OCSP / CRL / CRT / CPS / CPS_Nonice 多行文本) | | 操作日志 | logs.log 时间范围筛选 + 自动刷新 | | 调试专区 | 模拟 OCSP 请求(RFC 6960,CertID+Nonce,哈希算法可选 SHA1/256/384/512/SM3,验证响应签名与 Nonce 回显)+ 模拟时间戳请求(RFC 3161,随机数据按所选摘要算法构造 TimeStampReq,解析并验证 token 签名) | | SCT服务管理 | CT日志密钥管理(生成 ECDSA/RSA/SM2 密钥对、密钥列表 删除/启用/暂停,写 PKI/CTLOG/modpkica_ct_list.json)+ CT请求日志(只读 ctlogs.txt) | | 签名专区 | EXE 代码签名(时间戳类型 RFC3161/Authenticode + 时间戳地址可编辑下拉)+ PDF 文档签名(签章类型 Adobe/国密 35275/国密 38540/国密 0031 + 时间戳地址可编辑下拉),均含签名与验证 | ### API(32 个) `/api/status`、`/api/certs`、`/api/logs`、`/api/config`、`/api/chain`、`/api/certjson`、`/api/version`、`/api/issuers`、`/api/init`、`/api/makeroot`、`/api/signcert`、`/api/revoke`、`/api/restore`、`/api/rekey`、`/api/renew`、`/api/crl`、`/api/initocsp`、`/api/inittimestamp`、`/api/verify`、`/api/signcerts?kind=exe|pdf`(GET)、`/api/sign/exe`、`/api/sign/pdf`、`/api/verify/exe`、`/api/verify/pdf`、`/api/debug/ocsp`、`/api/debug/tsa`、`/api/ctlog/list`、`/api/ctlog/logs`、`/api/ctlog/gen`、`/api/ctlog/delete`、`/api/ctlog/enable`、`/api/ctlog/pause` --- ## PKI 目录与文件说明 | 路径 | 内容 | |---|---| | `PKI/ROOT/` | 根证书:`序列号.crt` + `序列号.key` | | `PKI/CA/` | 中间证书:`序列号.crt` + `序列号.key` | | `PKI/CERT/` | 终端证书 PEM:`序列号.crt` | | `PKI/KEY/` | 终端证书私钥:`序列号.key` | | `PKI/OCSP/` | OCSP 签名证书:`序列号_ocsp.crt` + `序列号_ocsp.key` | | `PKI/TIMSTAMP/` | TSA 证书:`类型_哈希算法.crt`(rsa_sha1 / rsa_sha256 / ecc_sha1 / ecc_sha256 / sm2_sm3)+ `.key` | | `PKI/WebPublic/CRT/` | 全部证书对外下载:`序列号.crt` | | `PKI/WebPublic/CRL/` | 全部 CRL 对外下载:`序列号.crl`、`序列号.der.crl` | | `PKI/CERTS.txt` | 证书登记表 | | `PKI/CONFIG.txt` | 系统配置 | | `PKI/logs.log` | 操作日志 | | `PKI/CTLOG/` | CT 日志密钥库 `modpkica_ct_list.json`(v3 风格,含 type 字段)+ 请求日志 `ctlogs.txt`(只追加) | --- ## 证书类型与命名规则 | 类型 | 含义 | 密钥存储 | 对外 | |---|---|---|---| | `R` | 根证书 | `ROOT/序列号.crt`、`ROOT/序列号.key` | `WebPublic/CRT/序列号.crt` | | `C` | 中间证书 | `CA/序列号.crt`、`CA/序列号.key` | `WebPublic/CRT/序列号.crt` | | `E` | 终端证书 | `CERT/序列号.crt`、`KEY/序列号.key` | `WebPublic/CRT/序列号.crt` | | OCSP 专用 | OCSP 签名 | `OCSP/序列号_ocsp.crt`、`_ocsp.key` | `WebPublic/CRT/序列号.crt` | | TSA 专用 | 时间戳签名 | `TIMSTAMP/类型_哈希.crt/.key` | `WebPublic/CRT/序列号.crt` | > 所有证书签发后同时按 `序列号.crt` 同步到 `WebPublic/CRT/`,供 `/CRT/{CID}.crt` 下载。 --- ## 配置文件 CONFIG.txt 位于 `PKI/CONFIG.txt`,无文件时 WebAdmin 系统配置页会自动创建默认内容: ```ini WEBPORT=80 OCSP=http://localhost:80/OCSP CRL=http://localhost:80/CRL/{CID}.crl CRT=http://localhost:80/CRT/{CID}.crt CPS=https://gitee.com/wxgshuju/modpkica CPS_Nonice=http://example.com/nonice ``` - `WEBPORT`:WEB 服务监听端口(默认 80) - `OCSP` / `CRL` / `CRT`:写入证书 AIA / CRL 分发点的 URL,支持 `{CID}` 占位符(替换为证书序列号) - `CPS`:证书策略 CPS URI - `CPS_Nonice`:证书策略用户通告(UserNotice)文本;WEB 面板多行输入,保存时换行以 `\n` 存储,签发证书时 `\n` 还原为真实换行 --- ## 登记表 CERTS.txt 位于 `PKI/CERTS.txt`,每行一条,空白分隔: ``` 序列号 类型 状态 原因编号 签发/吊销时间 颁发者序列号 密钥算法 ``` 示例: ``` 13fbe86586d1bca R V 0 2026/09/08-23:18:34 13fbe86586d1bca RSA 113521bfd E R 5 2026/09/09-00:53:25 1cdd332012fa2 RSA ``` - 类型:`R` 根 / `C` 中间 / `E` 终端 - 状态:`V` 正常 / `R` 已吊销 / `N` 未知 - 原因编号:0-9(吊销原因) - 时间格式:`yyyy/MM/dd-HH:mm:ss` > MAIN 在 OCSP / 时间戳处理前会重新加载 CERTS.txt,外部修改即时生效(无需重启)。 --- ## 操作日志 logs.log 位于 `PKI/logs.log`,**只追加**,每条记录包含:时间、时间戳、操作、命令、结果,记录间以**两个换行**分隔。所有操作(Admin 命令、WEB 请求、CRT/CRL/OCSP/时间戳请求、签名、摘要、验签过程)都会写入。 --- ## 常见问题 FAQ **Q1:编译/运行时中文乱码?** A:必须带 `-Dfile.encoding=UTF-8` 运行;源码编译已用 `-encoding UTF-8`。脚本注释使用英文避免 cmd 代码页问题。 **Q2:`Address already in use: bind`?** A:WEB 端口(默认 80)被占用。先 `netstat -ano | findstr :80` 找到 PID 结束旧进程,或改 `CONFIG.txt` 的 `WEBPORT`。 **Q3:OCSP 报"未知序列号"?** A:确认 CERTS.txt 有记录、`WebPublic/CRT/序列号.crt` 存在、`OCSP/颁发者序列号_ocsp.crt` 已生成(先运行 `initOCSP`)。 **Q4:Wosign 代码签名选 RFC3161 时间戳失败?** A:RFC3161 时间戳用于 PDF 文档签名;微软代码签名请选 **Authenticode** 类型(`/timestamp` 或 `/timestamp/sha1`)。 **Q5:签发 ECC 证书报错?** A:确认密钥位数按 ECC 曲线选择(256/384/521),且 BC 1.85 的 `BC185/` 完整放入项目根。 **Q6:SM2 已吊销证书 OCSP 验证报 `OCSP无法检查吊销状态 | 上报失败~`?** A:v1.1 已修复。根因是 SM2 分支手动 ASN.1 编码 `CertStatus.revoked` 误用 EXPLICIT,RFC 6960 要求 `[1] IMPLICIT RevokedInfo`,导致 openssl / BC 解析错位(`wrong tag` / `illegal object`)。RSA / ECC 已吊销证书不受影响(走 BC 高层 API)。升级后重启 MAIN 生效。 **Q7:如何离线开发?** A:所有依赖只有 `BC185/` 全家桶 + `lib/`(pdfbox),无 Maven 仓库;源码改完直接 `build.cmd` 编译,`out/` 即产物。 **Q8:国密 PDF 签章(35275 / 38540 / 0031)报错?** A:三者都**必须**使用 SM2 证书,且**必须**选择签章图片(png/jpg/bmp/gif)。GB/T 38540-2020 与 GM/T 0031-2025(`GM.sm2seal`)无标准时间戳扩展,即使填了时间戳地址也不嵌入;GB/T 35275-2017(`GM.sm2cms.detached`)为 CMS 结构可正常验签(SM3withSM2 含 ZA)。若用非 SM2 证书或未选图片,面板会给出明确中文错误。 **Q9:签名时时间戳地址填了外网 TSA 但失败?** A:离线 / 无外网环境会请求失败,但签名**不中断**——时间戳失败自动降级(日志记录原因),文件照常签名成功。连接外部 TSA 需本机可访问其 80/443 端口;留空则使用本机 MAIN 时间戳服务。 --- ## 更新记录 - **v1.23(2026-09-19)——SCT 多算法选组修复 + 国密 SCT 枚举对齐生产生态** - **修复 `isSm2Precert`(SM2 证书被误判为非 SM2)**:EC 公钥 SPKI 的算法 OID 固定为 `1.2.840.10045.2.1`(id-ecPublicKey),SM2 特性在 **named curve 参数 OID**(`1.2.156.10197.1.301` = sm2p256v1)——原实现只查外层算法 OID,SM2 证书永远匹配不到 → SCT 从 RSA/ECDSA 密钥池选组。修复:解析 SPKI ECParameters(SEQUENCE 首元素 / 裸 OID)取曲线 OID 判断;实测 `1135b7249`(SM2 曲线)=true、普通 ECC=false、RSA=false;新签发 SM2 证书 `1135b731c` 的 SCT 全部来自 SM2 池(SM2 TEST6/TEST2/TEST4,SM2-SM3,≤3 组) - **国密 SCT 枚举对齐生产生态**:验证零信(ZoTrus)SM2 SSL Pro 生产证书 `demo3.crt` 发现其 SCT 由零信国密 CT 日志系统(sm2ct.cn,参考 RFC 6962 实现)签发,实测枚举为 **hash=7(SM3) + sig=4**——原代码 sigName 无 sig=4 映射(显示 `TYPE4-SM3`),验签也只认 (7,7) 组合 - `sigName` 补映射:**sig=4 → SM2**(国密 CT 实现枚举,生产证书实测)+ 保留 sig=7(GB/T 38636-2020 / RFC 8998 枚举) - `verifySctSignature`:SM2 验签支持 **(7,4)** 与 **(7,7)** 两种组合 - `buildSerializedSct`:自家 SM2 签发枚举由 (7,7) 改为 **(7,4)**(对齐零信 sm2ct.cn 生产生态,外部验证器可识别) - **demo3.crt 验证结论**:3 条 SCT 的 log_id(15f210e3…、735700ef…、78a41057…)公钥不在本地密钥库、也不在 Google 权威 log_list.json(26 条)——属零信私有国密 CT 日志,显示"未知日志,无法验证"为正常;如需验签需补充零信日志公钥 - **WebAdmin 截图更新**:新增 `j.png`(SCT服务管理 CT日志密钥管理)、`k.png`(证书库验证弹窗 SCT 列表验签)、`stamp.png`(国密签章素材示例) - **v1.22(2026-09-18)——新增「SCT服务管理」:RFC 6962 CT 日志服务器 + 签发证书嵌入 SCT** - **新增 CtLog 核心类**(`src/modpkica/CtLog.java`):CT 日志密钥库 CRUD(ECDSA-P256 密钥写入 `PKI/CTLOG/modpkica_ct_list.json`,v3 风格结构 `operators[].logs[]`,含 `private_key` 字段)、RFC 6962 §3.1 预证书签名(`issuer_key_hash = SHA-256(颁发者 SPKI DER)` + 去 poison TBS)、SCTList TLS 编码组装、`ctlogs.txt` 追加 / 读取、PEM 证书解析 - **MAIN 新增 `/ct/add` 路由**:仅 POST;请求体为 PEM 格式「终端预证书 + 中间证书」(按序解析 ≥2 张);对每个 usable 密钥生成一条 SCT 并追加一行日志,返回二进制 SCTList - **WebAdmin 新增「SCT服务管理」页面**(14 个页面): - 密钥管理分组:生成 ECDSA-P256 密钥对(写 modpkica_ct_list.json)、密钥列表(描述 / log_id / 状态 / 创建时间),每行「删除 / 启用 / 暂停」操作按钮(删除 = 从文件移除;启用 = usable;暂停 = disable) - 请求日志分组:只读展示 `PKI/CTLOG/ctlogs.txt` 全部行(自带序号),格式 `SCT待签名数据取数据摘要||签名时的时间戳||终端证书使用者密钥标识符||终端证书授权密钥标识符||日志公钥的ID` - **MakeCert 改造**:移除自创扩展 OID `1.3.6.1.4.1.11129.2.4.5`;签发非 CA 证书且 EKU 含服务器/客户端认证时,改为「签发含 poison 的 SCT 预证书 → POST 自身 /ct/add → 取回 SCTList 嵌入 SCT 扩展 → 二次签名生成最终证书」,证书包含标准 SCT 列表(OID `1.3.6.1.4.1.11129.2.4.2`,双重 OCTET STRING + TLS 编码) - **冒烟测试(全部通过)**:MAIN 后台启动 → `/api/ctlog/gen` 生成密钥成功(log_id base64 = SHA-256(SPKI))→ `MakeCert` 签发 `1135b42cc.crt`(颁发者 `1cdd332014ecd`)走真实 HTTP `/ct/add` → ctlogs.txt 正确追加一行 → 证书含 1 条 SCT(ECDSA-SHA256,DER 签名)→ 用本地 modpkica_ct_list.json 作为日志公钥源,CertVerify SCT 验签 **通过(precert_entry)**;demo1 / demo2 官方日志验签回归 2/2 通过不受影响 - **修复**:`loadKeys` 段解析原从 log_id 后截取导致 `description`(在 log_id 之前)丢失,改按本条 log 对象整体截取后逐字段提取;修复 WebAdmin 文本块常量池溢出(PAGE_* 拆 4 段,每段 ≤65535 字节)与 P2/P3 文本块边界(P2 结束引号被 P3 定义吞掉)问题 - **v1.21(2026-09-18)——证书验证新增 SCT 列表签名验证(RFC 6962 / Certificate Transparency)** - **功能**:Web 面板「证书验证」在原有 OCSP / CRL / 证书签名 / 证书链验证基础上,新增对证书 SCT 扩展(OID `1.3.6.1.4.1.11129.2.4.2`)中**每一组 SCT 的日志签名验证**——不再只是解析展示 - **日志公钥数据**:读取项目根目录 `log_list.json`(Google 官方 CT 日志公钥数据集 v3,权威远程地址 `https://www.gstatic.com/ct/log_list/v3/log_list.json`,已离线下载到项目根;也支持 `PKI/log_list.json`、`PKI/WebPublic/log_list.json` 备选位置)。按 `log_id` 匹配日志描述与 `key`(base64 SPKI DER),日志缺失时明确提示"log_list.json 中无此日志" - **验证流程**:解析 SCT 扩展(双重 OCTET STRING 解壳 → SCTList)→ 逐条 SCT 提取版本 / log_id(32B) / 时间戳 / extensions / hashAlg / sigAlg / signature → 按 RFC 6962 §3.2 构造签名输入:`version + signature_type(0) + timestamp(8B) + entry_type(2B) + signed_entry + extensions(2B+数据)` - **precert_entry**(Web PKI 绝大多数场景):`issuer_key_hash(32B) = SHA-256(颁发者 SPKI DER)` + **TBS DER**——RFC 6962 §3.1 规定 `tbs_certificate` 为**最终证书 TBS 删除 SCT 扩展**(不含 poison 扩展),TBS 重建采用 BC 解析逐扩展剔除 SCT、其余原样保真重编码 - 失败时回退 **x509_entry**:`完整证书 DER` - **算法支持**:ECDSA-SHA256(DER 签名 + TLS 裸 r‖s 兼容)、RSA(SHA1/256/384/512),SM2/SM3(RFC 8998 枚举)预留 - **输出**:逐条显示 `通过/失败 | 日志=描述 | 算法=ECDSA-SHA256 | 时间戳=...(precert_entry)`,末尾汇总"通过 N 条,失败 M 条";结果打印 stdout,经现有链路自动联动 Web 面板验证弹窗展示 - **冒烟测试**(项目根样本): - `demo1.crt`(Google Trust Services WE2 签发 `*.gstatic.com`,2 条 SCT):Google 'Xenon2027h1' 通过、Sectigo 'Tiger2027h1' 通过(precert_entry)✓ - `demo2.crt`(DigiCert 签发 `www.boc.cn`,3 条 SCT):Google 'Argon2027h1'、DigiCert 'Wyvern2027h1'、DigiCert 'sphinx2027h1' 全部通过(precert_entry)✓ - **排查要点(供维护参考)**:SCT 验签曾全部失败,最终确认两个根因——① 错误地在 TBS 重建时补回 poison 扩展(RFC 6962 §3.1 明确 `tbs_certificate` **不含 poison**);② ECDSA 签名曾按裸 r‖s 转换,而实际日志签名为 **DER 编码**(先直接验 DER,失败再回退裸格式)。修复后 5 条 SCT 全部通过 - **v1.20(2026-09-17)——WebAdmin 改为多线程执行器 + 写操作串行锁** - **多线程响应**:`HttpServer` 执行器由单线程改为 8 线程固定线程池(`wa-worker` 守护线程)——读接口(status / certs / config / logs / chain / verify 等)并发响应,单个慢请求(如大日志)不再阻塞其他请求 - **写操作串行锁**:所有修改 PKI 数据的 API(config 保存、init、makeroot、signcert、revoke、restore、rekey、renew、crl、initocsp、inittimestamp、sign/exe、sign/pdf)统一走 `WRITE_LOCK` 全局锁串行执行,避免并发写证书 / 私钥 / CRL / CONFIG 造成文件竞争 - **日志线程安全**:`MLog.write` 加静态 `synchronized`,多线程下 logs.log 追加不会交错损坏 - **缓存加大**:`countLogLines` 缓存 10 秒 → **30 秒**、读缓冲 64KB → **256KB**(日志行数显示秒级精度无意义,30 秒缓存几乎不读文件) - **实测**:8 个并发请求(status×3 + config×2 + certs×2 + logs×1)全部 200;稳定延迟 status 3–8ms、certs 4ms;浏览器页面正常、console 无错误 - **v1.19(2026-09-17)——管理界面端口写入 CONFIG.txt(SUPORT)+ 修复大日志导致服务卡死** - **管理界面端口配置化**:WebAdmin 管理界面端口写入 `PKI/CONFIG.txt` 的 `SUPORT` 键(默认 `8080`,键名大小写不敏感) - 启动时自动确保:CONFIG.txt 不存在 → 按默认模板创建(含 `SUPORT=8080`);文件存在但无 SUPORT 项 → 自动追加 `SUPORT=8080` - **启动时以配置文件为准**:修改 `SUPORT` 后重启 WebAdmin 即监听新端口(端口优先级:系统属性 `modpkica.admin.port` → CONFIG.txt SUPORT → 默认 8080) - 系统配置页(系统设置)新增「SUPORT 管理界面端口」输入框,可读取 / 修改 / 保存,页面提示"修改保存后需重启 WebAdmin 生效" - 实测:保存 `SUPORT=8081` 后重启 → 监听 8081,/api/config 返回 suport=8081;改回 8080 重启恢复正常 - **修复:日志量大时 WebAdmin 全部请求超时(服务卡死)** - 现象:日志积累到 2 万+ 行后,/api/status、/api/config 等所有接口超时 - 根因:`countLogLines()` 用单字节 `InputStream.read()` 逐字节扫描整个 logs.log(200 万+ 次系统调用),而 HttpServer 是单线程执行器——仪表盘 3 秒一次的 `/api/status` 把唯一线程长期占住,后续请求全部排队超时 - 修复:改为 64KB 缓冲批量读 + 10 秒缓存(`lastLogCountTime/cachedLogLines`),日志行数不再每次全文件扫描;`tailFile`(尾部 N 行)本就是高效随机访问实现,保持不变 - 实测:21744 行日志下三个接口全部 200 快速响应 - 截图与使用方式不变 - **v1.18(2026-09-16)——修复 WebAdmin 独立启动时 MAIN 状态误判** - **问题**:`java modpkica.WebAdmin` 独立启动时,仪表盘 MAIN 状态显示矛盾——"MAIN: 未运行" 与 "● 运行中" 并存,且 MAIN 明明在运行却显示未运行 - **根因**:`mainPort` 静态字段初始值为字符串 `"未运行"`,`WebAdmin.start()` 原判定 `!isEmpty()` 把该占位串当作合法端口,导致 `mainRunning` 被误置为 true;且独立启动进程不持有 MAIN 的端口信息,状态永不刷新 - **修复**:①`WebAdmin.main()` 独立启动时探测本机 MAIN——读取 `CONFIG.txt` 的 `WEBPORT`(默认 80),TCP 连接成功则 `mainPort=实际端口`、`mainRunning=true`,否则 `未运行/false`;②`start()` 判定改为仅接受**数字端口**(`matches("\\d+")`),MAIN 内嵌启动传 "80" 正常、占位串不再误判;③`statusJson()` 每次被调用时按需重探测(3 秒缓存 `probeMainIfStale`),MAIN 启停后刷新页面/点刷新即可反映最新状态 - **实测**:MAIN 运行中(监听 80)→ 独立 WebAdmin 仪表盘显示 `MAIN: 80` + `● 运行中` ✓;console 无错误 - **v1.17(2026-09-16)——微软代码签名也改为批量 + 拖拽布局(与 PDF 一致)** - **微软代码签名页签改为与 PDF 相同的拖拽批量布局**:虚线拖拽区(ondragover/ondrop)+「选择文件」多选按钮(`multiple`,accept exe/dll/sys/cab/cat/msi);待签名列表逐行显示文件名 / 大小 / 状态(待处理 / 签名中 / 成功 / 失败),行内单独签名 / 验证 / 移除,成功后行内下载链接;顶部「批量签名」「批量验证」「清空」串行处理全部并汇总(成功 N / 失败 M + 逐条错误);按文件名 + 大小去重、非 PE 扩展名自动过滤 - 参数区保留:签名证书(代码签名 EKU 下拉)、时间戳类型(RFC3161 / Authenticode)、时间戳请求地址(留空 = 本机 MAIN / 外部 TSA 可编辑下拉) - 请求参数与后端一致(`/api/sign/exe`:serial/tsType/tsUrl/file/name;`/api/verify/exe`:file),实测 `notepad.exe`(RSA SHA256)签名返回 200 且附加 RFC3161 时间戳(3229B) - 移除旧单文件版 `signDo/verifyDo/signExeDo/verifyExeDo`(死代码);JS 队列复用 `fmtSize/esc` 等工具函数 - 截图更新:`f.png` 替换为微软代码签名批量布局(拖拽区 + 2 文件待签名列表) - **v1.16(2026-09-16)——签名专区选择夹布局 + PDF 批量拖拽签名** - **签名专区改造为选择夹(tab)形式**:「微软代码签名」与「PDF 文档签名」两个页签各占整页,空间更大;全局字体 14px → 15px,输出区、列表、按钮同步放大 - **PDF 文档签名支持批量与拖拽**:虚线拖拽区(ondragover/ondrop)+ 「选择 PDF 文件」多选按钮(`multiple`);待签名列表逐行显示文件名 / 大小 / 状态(待处理 / 签名中 / 成功 / 失败),行内可单独签名 / 验证 / 移除,成功后行内显示下载链接;顶部「批量签名」「批量验证」串行处理全部文件并汇总(成功 N 个 / 失败 M 个 + 逐条错误);按文件名 + 大小去重,非 PDF 自动过滤 - 修复 Java text block 内 JS 正则转义(`\.` → `\\.`)与 `\n` 字面量(`'\n'` → `'\\n'`),消除编译错误与运行时 JS SyntaxError;签名 API(`/api/sign/pdf`、`/api/verify/pdf`)参数格式不变(serial/sealType/tsUrl/image/file/name) - 截图更新:`f.png` 替换为选择夹 + PDF 拖拽区新布局 - **v1.15(2026-09-16)——国密 GM/T 0031-2025 修正为与 GB/T 38540-2020 一致的数据结构** - **问题**:v1.14 按 GM/T 0031-2014 的两层结构(`SES_Signature{toSign, signature}` + `SESeal{esealInfo, signInfo}` + `header.version=11`)实现,权威工具识别为 **"SESv11"(GM/T 0031-2014)**,不是 0031-2025 - **依据**:国家密码管理局第 54 号公告(2026-01-05)发布 GM/T 0031-2025《安全电子签章密码技术规范》,**2026-07-01 实施并废止 2014 版**;标准牵头单位(北京数字认证)官方解读明确:**"本次标准充分考虑与 GB/T 38540-2020 标准的兼容性和延续性,在保证数据结构一致的前提下有所扩展"**——即 0031-2025 的电子印章/电子签章数据结构与 GB/T 38540-2020 一致(4 字段 SESeal、TBS_Sign 5 字段、SES_Signature 4-5 字段、header.version=4) - **修正**:`signPdfGm` 中 0031 分支直接复用 **GB/T 38540 数据结构**(`buildGmSealInfo38540` + `buildGm38540Signature`),SubFilter 仍为 `GM.sm2seal`、签名理由显示"GM/T 0031-2025 国密电子签章";删除 2014 两层结构的 `buildGm0031Signature`/`buildGm0031TimeInfo`/`buildGmSealInfo0031`;**时间戳与 LTV 沿用 38540 方案**(timeStamp[0] BIT STRING 绑定签名值 + DSS 同保存) - **验证端兼容**:`verifyGmSeal38540` 保留对旧版 0031(2014 两层结构)的解析(字段数=2 分支),新结构(4-5 字段)走 38540 分支,两种样本都能验证 - **实测(113548382 SM2 文档签名证书,本机 MAIN /rfc3161)**:TBS_Sign 5 字段、version=4(权威工具应识别为 SESv4/2025 版而非 SESv11);`SM2 签名验证: 通过 ✓`;`印章签名验证: 通过 ✓`;`签名时间戳(timeStamp 字段): TSA=… 绑定签名值(SM3): 通过 ✓ TSA 证书验签: 通过 ✓`;`LTV: 已启用(DSS 3/3/2)`;最终结论:国密 PDF 电子签章有效 ✓ - 演示产物:`MODPKICA_v115_GM0031_2025结构_时间戳_LTV.pdf`(桌面,供权威工具复核) - **v1.14(2026-09-16)——国密 GM/T 0031(2014 两层结构版本,被 v1.15 取代)** - **标准依据**:GM/T 0031-2014《安全电子签章密码技术规范》(官方全文已下载核实;GM/T 0031-2025 已于 2026-07-01 实施并废止 2014 版,2025 版全文未公开,按其前身 2014 版可查证的 ASN.1 结构实现) - **结构核对结论(vs 2014 版标准,逐项)**: - `SES_Signature ::= SEQUENCE{ toSign TBS_Sign, signature BIT STRING }`(2 字段)✓ - `TBS_Sign` 7 字段:`version/eseal/timeInfo/dataHash/propertyInfo/cert/signatureAlgorithm` ✓ - `timeInfo ::= BIT STRING`——标准原文"可以是时间戳,也可以是 UTCTIME 时间"→ **支持嵌入时间戳** ✓(已实现) - `SESeal ::= SEQUENCE{ esealInfo SES_SealInfo, signInfo SES_SignInfo }` ✓ - `SES_SealInfo.property` = 6 字段(type/name/certList/createDate/validStart/validEnd,**UTCTIME**)✓ - `SES_SignInfo.signData` = 制章人对 **SEQUENCE{ 印章信息, 制章人证书, 签名算法标识 }** 的签名(非仅印章信息)✓ - `extDatas` 为 **EXPLICIT [0]**(A0 包 SEQUENCE)——**本次修正**(原为无标签 SEQUENCE) - **本次变更**: 1. `buildGmSealInfo0031`:extDatas 改为 `DERTaggedObject(true, 0, …)`(标准 EXPLICIT [0]) 2. `buildGm0031TimeInfo`:timeInfo 支持时间戳——`tsUrl` 非空时 BIT STRING 内容 = **完整 TimeStampToken DER**;因 timeInfo 是被签名 TBS_Sign 的组成部分、先于签名值存在,时间戳 **messageImprint = 原文 SM3 杂凑(dataHash)**(避免循环依赖,等价证明原文在 TSA 时间已存在);失败降级 UTCTime 不阻断 3. **LTV**:0031 也装 DSS(DSS 是 PDF 层标准 ISO 32000,与签名格式无关),同一次保存(签名后零增量) 4. `verifyGmSeal38540` 0031 分支:新增**印章签名自检**(按 SEQUENCE{印章信息,证书,算法标识} 验签)+ **timeInfo 时间戳解析**(TimeStampToken 优先、UTCTime 回退;绑定原文杂凑 + TSA 验签) - **实测(113548382 SM2 文档签名证书,本机 MAIN /rfc3161)**:TBS_Sign 7 字段;`SM2 签名验证: 通过 ✓`;`印章签名验证(制章者…): 通过 ✓`;`签章时间信息(timeInfo): TSA=… 时间=2026-09-16 23:02:09 算法=1.2.156.10197.1.401(SM3)`;`绑定原文杂凑(SM3): 通过 ✓ TSA 证书验签: 通过 ✓`;`LTV: 已启用(DSS Certs=3/OCSPs=3/CRLs=2)`;最终结论:国密 PDF 电子签章有效 ✓ - 演示产物:`MODPKICA_v114_GM0031_时间戳_LTV.pdf`(桌面,供权威工具复核) - **答复用户问题**:①样本符合标准(已按 2014 版权威结构逐项核对并修正 extDatas EXPLICIT 标签);②**支持嵌入时间戳**(timeInfo 字段,标准明文允许);③**支持 LTV**(DSS 字典,PDF 层标准) - **v1.13(2026-09-16)——修正 GB/T 38540 timeStamp 字段编码为标准 `[0] BIT STRING` + 印章签名自检** - **问题**:v1.12 把 timeStamp 编码成 universal OCTET STRING(tag 0x04),而标准 7.1.1 定义为 **`timeStamp[0] BIT STRING OPTIONAL`**(对签名值的时间戳,GB/T 20520 = RFC3161,DER 编码存放;ASN.1 无模块级 TAGS 声明时默认 EXPLICIT 标签)——权威验章工具按 `[0]` 上下文标签找时间戳,认不出 0x04 → 不显示时间戳(显示本地时间),并把异常字段计入解析导致印章验证报错 - **修正**:`buildGm38540Signature` 时间戳改为 **EXPLICIT [0](A0)包 BIT STRING**(`new DERTaggedObject(true, 0, new DERBitString(tok))`);SES_Signature 第 5 字段实测 tag=`DLTaggedObject tag=0x0` ✓ - **印章签名自检**:`verifyGmSeal38540` 新增按 GB/T 38540 6.1.5 验证 SESeal.signedValue(制章者对 eSealInfo SEQUENCE 的 SM3withSM2 签名,用 SESeal.cert 公钥)——本机实测印章签名验证**通过 ✓**,证明印章签名本身正确,权威工具之前报"印章签名错误"系 timeStamp 字段类型错误引发的连锁解析异常 - **时间戳解析兼容**:支持 EXPLICIT [0](A0 包 03/04)、IMPLICIT [0](80/81)、universal BIT STRING(03)、旧 OCTET STRING(04)四种编码;绑定签名值(SM3)+ TSA 证书验签 - **实测(113548382 SM2 文档签名证书,本机 MAIN)**:SES_Signature 5 字段;`印章签名验证(制章者 …): 通过 ✓`;`签名时间戳(timeStamp 字段): TSA=… 算法=1.2.156.10197.1.401 绑定签名值(SM3): 通过 ✓ TSA 证书验签: 通过 ✓`;`LTV: 已启用(DSS Certs=3/OCSPs=3/CRLs=2)`;最终结论:国密 PDF 电子签章有效 ✓ - 演示产物:`MODPKICA_v113_GM38540_时间戳_标准timeStamp.pdf`(桌面,供权威工具复核) - **v1.12(2026-09-16)——国密 GB/T 38540-2020 支持嵌入时间戳与 LTV** - **标准依据**:GB/T 38540-2020 **7.1.6 时间戳**明确定义——SES_Signature 可选第 5 字段 `timeStamp`(对签名值的时间戳,符合 GB/T 20520 规定 = RFC3161,时间戳格式按 DER 编码存放;验证流程 h) 要求验证时间戳有效性,比对时间戳时间与签章时间);参考实现(PDF国密GBT 38540-2020(4))只做了 4 字段、未实现该字段,本次按标准补齐 - **时间戳**:`buildGm38540Signature` 生成 SES_Signature 后取 SM2 签名值 → **SM3 摘要** → RFC3161(SM3)时间戳请求 → token DER 作为第 5 字段 `timeStamp`(OCTET STRING)加入;失败降级无时间戳(日志) - **LTV**:DSS 是 PDF 层标准(ISO 32000 DSS 字典),与签名格式无关——`signPdfGm` 的 38540 分支同样 `collectLtvData` + `installDss` 同保存(被签名覆盖,签名后零增量);GM/T 0031 保持不装(结构无对应约定) - **verifyGmSeal38540**:接受 4/5 字段结构;时间戳解析(TSA/时间/算法/**绑定签名值(SM3)**/TSA 证书验签),时间戳无效则最终结论为无效 - **实测(113548382 SM2 文档签名证书,本机 MAIN)**:SubFilter=`GM.sm2seal`,SES_Signature 5 字段;`签名时间戳(timeStamp 字段): TSA=… 时间=2026-09-16 22:24:50 算法=1.2.156.10197.1.401 绑定签名值(SM3)=通过 TSA 证书验签=通过`;`LTV: 已启用(DSS Certs=3/OCSPs=3/CRLs=2)`;dataHash 比对 + SM2 验签 + 时间戳全部通过 - 演示产物:`MODPKICA_v112_GM38540_时间戳_LTV.pdf`(桌面) - **v1.11(2026-09-16)——国密 GB/T 35275-2017 支持嵌入签名时间戳(SM3)与 LTV** - **标准依据**:GB/T 35275-2017 是基于 CMS(SM2 密码算法加密签名消息语法)的国密实现,SignerInfo 的 **unsignedAttrs 为通用结构**——CAdES-T 签名时间戳(`id-aa-timeStampToken`)可正常承载;DSS/LTV 是 PDF 层标准(ISO 32000 DSS 字典),与签名算法无关,任意 SubFilter 均可用 - **时间戳**:`buildGm35275Signature` 生成 CMS 后取 SM2 签名值 → **SM3 摘要**(SM2/SM3 证书固定 SM3)→ RFC3161 时间戳请求(`requestTimestamp` 新增摘要算法参数,SM3 OID `1.2.156.10197.1.401`)→ token 写入 unsignedAttrs → `replaceUnsignedAttributes` + `replaceSigners` 重建;失败降级无时间戳签名(日志) - **LTV**:`signPdfGm` 的 35275 分支在保存前 `collectLtvData`(SM2 证书链 OCSP + CRL)+ `installDss`,与签名同一次保存(被签名覆盖,签名后零增量);38540/0031 的 SES_Signature 无标准 LTV 扩展,不装 DSS - **verifyPdf**:时间戳绑定校验按 token 哈希算法**自适应**(新增 `boundToSignature`:SHA-256/SHA-224/SHA-384/SHA-512/SM3),SM3 时间戳正确显示"绑定签名值=true" - **实测(113548382 SM2 文档签名证书,链 SM2CA→ROOTCA,本机 MAIN)**:SubFilter=`GM.sm2cms.detached`;`签名时间戳(CAdES-T unsignedAttrs): TSA=… 时间=2026-09-16 22:11:31 算法=1.2.156.10197.1.401 绑定签名值=true`;`LTV: 已启用(DSS Certs=3/OCSPs=3/CRLs=2)`;CMS 验签 + SM3 摘要比对 + 证书链全部通过 - 演示产物:`MODPKICA_v111_GM35275_时间戳_LTV.pdf`(桌面) - **v1.10(2026-09-16)——PDF Adobe 签名时间戳位置修正:按权威工具样本对齐 CAdES-T(unsignedAttrs)** - **背景**:v1.9 把时间戳放 signedAttrs(PAdES-BES 属性方式),用户权威验证工具判"签名不包含嵌入的时间戳" - **样本逆向**:解析用户提供的权威工具签名样本 `签名嵌入时间戳无LTV.pdf`(Wosign 类工具,TSA 恰为本机 MODPKICA SHA256 时间戳证书)——其 SignerInfo **无 signedAttrs**,时间戳 token 位于 **unsignedAttrs** 的 `id-aa-timeStampToken`(1.2.840.113549.1.9.16.2.14),且 **messageImprint = SHA-256(签名值)**(非待签数据摘要)——这正是 **CAdES-T / PAdES-T 签名时间戳**的标准位置(ETSI TS 101 733) - **重构 `buildCms`**:时间戳不再进 signedAttrs——先生成 CMS(BC 默认 signedAttrs:contentType/messageDigest/signingTime),取 SignerInfo 签名值 → SHA-256 摘要 → 请求 RFC3161 → token 放入 unsignedAttrs 属性 → `SignerInformation.replaceUnsignedAttributes`(BC 静态 API)+ `CMSSignedData.replaceSigners` 重建 CMS;时间戳失败仍降级无时间戳签名(日志) - **verifyPdf 同步**:签名时间戳解析改查 **unsignedAttrs**,输出 TSA/时间/算法/`绑定签名值=true|false`(imprint 对签名值比对) - **实测(113548234 RSA 文档签名证书,链 EVCA→ROOTCA,本机 MAIN)**:`签名时间戳(CAdES-T unsignedAttrs): TSA=… 时间=2026-09-16 21:30:36 算法=SHA-256 绑定签名值=true`;独立探针确认 unsignedAttrs 含 id-aa-timeStampToken、**imprint==SHA-256(签名值)=true**(与权威工具样本同构);DSS Certs=3/OCSPs=3/CRLs=2 保留;CMS 验签 + 摘要比对 + 证书链全部通过 - **纯文档时间戳(无签名证书)补上 LTV**:修复 `extractTsaChain` 的 BC 泛型编译错误(`Store.getMatches` 擦除问题,此前 addDocTimeStamp 的 LTV 逻辑从未编译进 out,纯 DocTimeStamp 一直无 DSS)——现探测请求取 TSA 证书链 → `collectLtvData`(链上证书 OCSP 响应 + 根 CRL)→ `installDss` 与时间戳同一次保存(被 DocTimeStamp 覆盖,签名后零增量);实测 `LTV: 已启用(DSS Certs=2/OCSPs=2/CRLs=1)`,messageImprint 比对通过、TSA 验签通过 - 演示产物:`MODPKICA_v110_签名内嵌时间戳_CAdES-T.pdf` 与 `MODPKICA_v110_纯文档时间戳_LTV.pdf`(桌面,可用权威工具复核) - **v1.9(2026-09-16)——PDF Adobe 标准签名时间戳策略重构:签名内嵌时间戳,独立 DocTimeStamp 仅用于无证书纯时间戳** - **时间戳与签名不分离(用户明确要求)**:选中数字证书签名时,不再叠加独立 DocTimeStamp 签名字段——时间戳按 PAdES-BES 标准**嵌入数字签名内部**(signedAttrs 的 `id-aa-timeStampToken`,1.2.840.113549.1.9.16.2.14 属性),**文档只有一个签名字段(adbe.pkcs7.detached)** - **纯文档时间戳(无签名证书)**:PDF 签名区证书下拉新增"(不签名,仅文档时间戳)"选项——不选证书时调用 `SignTool.signPdfTimestampOnly`,生成独立 DocTimeStamp 签名字段(Filter=Adobe.PPKLite、SubFilter=`ETSI.RFC3161`、Contents=RFC3161 TimeStampToken);此时不可附加签章图片、不可选国密签章 - **LTV 同保存(签名后零增量)**:DSS(Certs 证书链 / OCSPs / CRLs)与数字签名**同一次保存**,全部落在签名 ByteRange 覆盖区内——数字签名是文档最后一次修改,无"签名区外内容增加"告警;因数字签名 ByteRange 无法在保存前预知(PDFBox 增量保存会改写 trailer `/ID[1]`,MD5 含时间),**省略 VRI 索引字典**(PAdES 允许),验证器按证书/响应内容匹配 DSS 数据 - **verifyPdf 新增"签名时间戳"解析**:主签名 CMS 的 signedAttrs 中若含 `id-aa-timeStampToken`,输出 TSA 名称 / 时间戳时间 / 摘要算法(TimeStampToken 专用解析) - **实测(1135228a2 RSA,链 EVCA→ROOTCA,本机 MAIN,双场景)**: - 选中证书:签名字段=1(adbe.pkcs7.detached);`签名时间戳(id-aa-timeStampToken): TSA=… 时间=2026-09-16 20:54:07 算法=SHA256`;DSS Certs=3/OCSPs=3/CRLs=2/无 VRI;CMS 验签 + 证书链通过,最终"PDF 签名有效" - 纯时间戳:签名字段=1(ETSI.RFC3161);messageImprint 比对通过、TSA 证书验签通过,最终"PDF 文档时间戳有效(未附加数字签名)" - WebAdmin PDF 签名区逻辑:`serial` 空 → 纯文档时间戳(signPdfTimestampOnly);`serial` 非空 → signPdf(内嵌时间戳属性);前端同步校验(纯时间戳禁签章图片/禁国密) - **v1.8(2026-09-16)——PDF Adobe 标准签名 PAdES 重构:DocTimeStamp 文档时间戳 + LTV 同保存,签名后零增量** - **文档时间戳(DocTimeStamp)**:Adobe 标准签名现在**先盖文档时间戳**——独立签名字段,Filter=Adobe.PPKLite、SubFilter=`ETSI.RFC3161`、Contents=RFC 3161 TimeStampToken(覆盖整个文档)。Acrobat 据此显示"签名时间来自 TSA"而非本地时间(修复"签名时间来自本地、不含时间戳")。时间戳地址留空(= 本机 MAIN)或指定外部 TSA 均可;请求失败自动降级(跳过时间戳继续签名,原因写 logs.log) - **顺序重构(修复"签名控制区未经修改但有内容增加")**:原实现"数字签名 → 签名后增量追加 DSS"造成签名后追加未签名内容,严格工具告警。现改为 **PAdES 部分签名顺序:① DocTimeStamp(覆盖原文档)→ ② 数字签名(adbe.pkcs7.detached)与 DSS/VRI 同一次保存**——数字签名是文档**最后一次**修改,其 ByteRange 覆盖含 DocTimeStamp 与全部 LTV 数据的整个文档,签名后零增量 - **DSS(Document Security Store)**:与数字签名同一次保存写入,全部被签名覆盖: - `/DSS/Certs`:签名证书链 DER(去重) - `/DSS/OCSPs`:链上每张证书的 OCSP 响应(RFC 6960 完整 OCSPResp;CertID=SHA-1 + Nonce;地址 = CONFIG.txt `OCSP`,缺省 `http://127.0.0.1:WEBPORT/OCSP`) - `/DSS/CRLs`:链上每个 CA/根的 CRL(DER;优先 CONFIG `CRL` 替换 `{CID}` 的 `.der.crl` 变体,回退原 URL 与本地 `PKI/WebPublic/CRL/序列号.der.crl`) - `/DSS/VRI`:key = **DocTimeStamp 的 ByteRange SHA-1 大写 hex**(PAdES 规范;时间戳失败降级时省略 VRI,DSS 数组仍全局可用);VRI 引用 DSS 中对应 OCSP/CRL/Cert - `verifyPdf` 升级:**逐个验证全部签名字段**——DocTimeStamp 走专用解析(TimeStampToken:messageImprint 与 ByteRange 摘要比对 + token 内 TSA 证书验签 + 时间戳时间/序列号输出),数字签名走 CMS 验签 + 文档摘要比对 + 证书链;输出含 LTV 状态(DSS Certs/OCSPs/CRLs/VRI) - **实测(1135228a2 RSA,链 EVCA→ROOTCA,本机 MAIN)**:签名字段 2 个(ETSI.RFC3161 + adbe.pkcs7.detached);DocTimeStamp messageImprint 比对通过、TSA 签名验证通过;数字签名 CMS 验签/摘要比对/证书链三级全部通过;DSS Certs=3/OCSPs=3/CRLs=2/VRI=DocTimeStamp SHA1 命中;OCSP `responseStatus=0`、CRL 可解析;数字签名后无任何增量 - **v1.7(2026-09-15)——PDF 签名专区新增第四种签章类型:国密 GM/T 0031-2025《安全电子签章密码技术规范》** - **标准澄清**:需求中"GB/T 0031-2025"实际为 **GM/T 0031-2025**(密码行业标准,非 GB/T 国标;国家密码管理局公告第 54 号发布,2026-07-01 实施,替代 GM/T 0031-2014)。2025 版全文未公开(行业标准不免费发布),实现依据 = **GM/T 0031-2014 官方标准原文**(gmbz.org.cn)公开的 ASN.1 + 2025 版公告确认的兼容演进,交付如需对拍正式文本以官方为准 - **与 GB/T 38540-2020 的结构差异**(August 技术博客 + 2014 官方原文交叉确认): | 层次 | GB/T 38540-2020 | GM/T 0031 | |---|---|---| | 电子印章 SESeal | { eSealInfo, **cert, signAlgID, signedValue** } 4 子节点 | { SES_SealInfo, **SES_SignInfo{cert, signatureAlgorithm, signData}** } 2 子节点 | | 印章属性 | { type, name, **certListType**, certList(证书 DER), ... } | { type, name, certList(SEQUENCE OF **certID=IA5String 证书序列号**), ... },无 certListType | | 电子签章 SES_Signature | { TBS_Sign, cert, signatureAlgID, signature } 4 子节点 | { TBS_Sign, signature } 2 子节点 | | TBS_Sign | { version, eseal, timeInfo(GeneralizedTime), dataHash, propertyInfo } 5 字段 | { version, eseal, timeInfo(**BIT STRING**,时间戳或 UTCTIME), dataHash, propertyInfo, **cert, signatureAlgorithm** } 7 字段 | | 判别法 | 签章第 2 子节点 OCTET STRING;印章 4 子节点 | 签章第 2 子节点 BIT STRING;印章 2 子节点 | | 制章签名对象 | SES_SealInfo 本体 | SEQUENCE{ SES_SealInfo, cert, signatureAlgorithm } | - **新增构造器**:`buildGmSealInfo0031`(SES_SealInfo:header{ID="ES",version=11,Vid="MODPKICA"} / esID / property{type=1,name,certList 三项证书序列号,createDate/validStart/validEnd=UTCTIME} / picture / extDatas 政务 OID);`buildGm0031Signature`(SES_Signature:SESeal 两层嵌套 → TBS_Sign 7 字段 → SM2 对 TBS_Sign 完整 DER 签名);`signPdfGm` 三路分发(38540/0031/35275),0031 的制章签名单独按 SEQUENCE{SealInfo,cert,OID} 计算;`signPdf` 入口白名单加入 `GM0031` - **PDF 集成**:Filter=`Adobe.SM2`、**SubFilter=`GM.sm2seal`**(GM/T 0112-2021《PDF 格式文档的密码应用技术要求》规定的国密电子签章子过滤器,与 38540 共用,验证端按签章子节点类型自动判别 38540/0031)、DM/SM 属性=SM3/SM2 OID、Contents=SES_Signature DER 十六进制;时间戳按 GM/T 0112 建议附签章尾部,本实现 timeInfo 内嵌 UTCTIME(签章时间),外部时间戳扩展沿用现有"仅 ADOBE/GM35275 嵌入"规则 - **验证侧**:`verifyGmSeal38540` 升级为判别式——SES_Signature 4 子节点走 38540 分支(证书/算法在【外层】),2 子节点走 0031 分支(证书/算法在【TBS_Sign 内部】tbs[5]/tbs[6]),两者 dataHash 均取 TBS_Sign[3] 与 SM3(ByteRange) 比对 + 证书公钥 SM3withSM2 验签 TBS_Sign 完整 DER - **实测(113548382 SM2 证书,链 SM2CA→ROOTCA)**:`sealType=GM0031` 签名 → 产物 SubFilter=GM.sm2seal、SES_Signature 2 子节点、TBS_Sign 7 字段、SESeal 2 子节点、certList 三项证书序列号(113548382/1cdd33203a2ce/13fbe86586d3f90)、version=4、header version=11;验证接口输出「TBS_Sign 字段数: 7」「文档摘要比对: 通过 ✓」「SM2 签名验证: 通过 ✓」「最终结论: 国密 PDF 电子签章有效 ✓」;GM38540(5 字段)与 GM35275(CMS)回归均通过 - WebAdmin:PDF 组「签章类型」下拉新增 `国密 GM/T 0031-2025`;`sealLabel` 四路映射;hint 说明更新;签章仅限 SM2 证书且必须选择签章图片 - **v1.6(2026-09-15)——Authenticode 时间戳嵌入方式修复 + 国密 PDF 签名空间修复** - **国密 PDF 签名空间修复**:GB/T 35275-2017 与 GB/T 38540-2020 签名时若印章图片较大(> ~55KB),签名 Contents(印章图片字节 + 证书链 + ASN.1 结构)会超过固定预留的 65536 字节,触发 `java.io.IOException: Can't write signature, not enough space`。修复:`signPdfGm` 按实际输入**动态计算预留空间**(`pref = max(65536, 图片字节+证书链+8192) × 1.5 + 32768`)。实测 2MB 印章图两种国密标准均签名成功(输出 8.4MB 增量 PDF);小图场景不变(最小仍 64KB) - **问题**:签名专区选择「Authenticode」时间戳时,签名操作本身成功(日志显示"已附加 Authenticode 时间戳"),但 Windows 原生验证失败:`signtool verify /pa /v` 报 `0x80091004`(File is not timestamped)→ 修复后进一步暴露 `0x80096003`(BAD_DIGEST),两者根因不同: - **0x80091004 根因**:旧实现把 RFC3161 的 `SPC_RFC3161_TS`(`1.3.6.1.4.1.311.3.3.1`)属性嵌入完整 ContentInfo,Windows 只认老式 **countersignature(`1.2.840.113549.1.9.6`)** 机制。用 signtool 自身产物 asn1parse 对照确认:老式时间戳 = unsignedAttrs 中 `countersignature` 属性,其值为 token 内**首个 SignerInfo**(不是完整 token) - **0x80096003 根因**:countersignature 的签名需要 **TSA 证书**参与验证,但主 CMS 证书集合只放了签名证书链,未并入 token 内的 TSA 证书(signtool 会合并)→ 修复:`extractCertificates` 从 token `SignedData` 提取证书(注意 `certificates` 为 **IMPLICIT [0]**,需 `ASN1Set.getInstance(tagged, false)`),按序列号去重后并入主 CMS 证书集合 - **请求侧对齐**:新增 `SignTool.requestAuthenticodeTimestamp(sig, baseDir, tsUrl)`——请求体 = base64( `ContentInfo{1.3.6.1.4.1.311.3.2.1, SEQUENCE{id-data, [0] OCTET STRING(主签名值)}}` ),与 signtool `/t` 及 Go 版 MAIN `/timestamp` 提取协议(0x04 0x81/82/83 定位被签名内容)完全一致;响应兼容 DER 直返与 base64 PEM 正文两种 - **嵌入侧**:`buildExeCms` 对 AUTHENTICODE 类型走 countersignature 分支(提取 token 首个 SignerInfo 嵌入 `1.2.840.113549.1.9.6`),RFC3161 仍走 `SPC_RFC3161_TS` 完整 token 分支,互不影响 - **实测(Windows 原生)**:两种模式均 `signtool verify /pa /v` → 0 错误,`The signature is timestamped`,`Timestamp Verified by ROOTCA By Timstamp SHA256`;Authenticode 产物 asn1parse 与 signtool 自身产物逐字段一致(signedAttrs 含 contentType / signingTime / CMSAlgorithmProtection / messageDigest=SHA256(主签名值) / id-data=主签名值) - **说明**:外部 TSA(DigiCert 等)选择 Authenticode 类型时,若其响应为 `TimeStampResp`(PKIStatus 包装)或直接 `ContentInfo`,`extractFirstSignerInfo` 均兼容;请求失败自动降级不影响主签名 - **v1.5(2026-09-15)——签名专区:时间戳类型 / 签章类型 / 国密 PDF 电子签章** - **EXE 代码签名组新增「时间戳类型」下拉**(RFC3161 / Authenticode):RFC3161 → 标准 TimeStampToken(POST `application/timestamp-query`,默认本机 MAIN `/rfc3161`);Authenticode → 微软老式 PKCS#7(请求体 base64 文本,默认本机 MAIN `/timestamp`)。`SignTool.requestTimestamp` 增加类型/地址重载:Authenticode 响应兼容 DER 直接返回与 base64 PEM 正文(本机 MAIN 按 Go 版返回 PKCS#7 DER 的 base64,需解码后再嵌入 unsignedAttrs `1.3.6.1.4.1.311.3.3.1`) - **EXE / PDF 两组新增「时间戳请求地址」可编辑下拉**:留空 = 本机 MAIN(WEBPORT 读 CONFIG.txt);内置 DigiCert / Symantec / Comodo / Certum 四个外部 TSA 快捷选项,可手输任意地址。实测连接 `http://timestamp.digicert.com` 成功获取真实 RFC3161 时间戳(5986B)并嵌入;请求失败自动降级(签名成功、日志记录原因) - **PDF 组新增「签章类型」下拉**(三种): - **Adobe 标准文档签名**(默认,`adbe.pkcs7.detached`):支持 RSA / ECC / SM2;`buildCms` 经 `DefaultSignedAttributeTableGenerator` 在 signedAttrs 附加 `id-aa-timeStampToken`(`1.2.840.113549.1.9.16.2.14`,值为 RFC3161 时间戳令牌),BC 自动补 contentType / messageDigest;实测 CMS 验签、文档摘要比对、证书链全部通过 - **国密 GB/T 35275-2017**(`GM.sm2cms.detached`,仅 SM2 证书):Contents 为标准 CMS SignedData(detached),SESeal 电子印章挂 signedAttrs 私有 OID `1.2.156.112600.8.1`(eSealInfo + 印章证书链块 + SM2 OID + 制章签名);Filter=`Adobe.SM2`、DM/SM 属性=SM3/SM2 OID;证书链完整写入 CMS certificates;实测 CMS 验签(SM3withSM2 含 ZA)、文档摘要比对、证书链通过 - **国密 GB/T 38540-2020**(`GM.sm2seal`,仅 SM2 证书,必须选签章图片):Contents 手工 ASN.1 构造 **SES_Signature**(SEQUENCE{ toSign TBS_Sign, cert, signatureAlgID=SM2 OID, signature });TBS_Sign=SEQUENCE{ version=4, eseal SESeal, GeneralizedTime, dataHash=SM3(ByteRange), propertyInfo };SESeal=eSealInfo(header/esID/property 含证书列表与有效期/picture/extDatas 政务 OID)+ 制章证书 + SM2 OID + 制章签名;`setPreferredSignatureSize(65536)` 预留大签名区;验证新增 `verifyGmSeal38540` 专用解析:dataHash 与 SM3(ByteRange) 比对 + 证书公钥 SM3withSM2 验签 TBS_Sign 完整 DER,实测两项全部通过 - 国密 PDF 印章信息构建:`buildGmSealInfo38540`(SES_SealInfo 五段)/ `buildGmSealInfo35275`(sealId/UUID/certInfo/imgInfo/ext),制章签名与文档签名均走 `Signature.getInstance("SM3WITHSM2","BC")`(BC 内置 ZA 计算,符合 GB/T 32918);参考实现:`java-lening\PDF或者OFD国密SM2签名验证\多个版本\PDF国密GBT 38540-2020(4)` 与 `PDF国密SM2公安政务兼容GBT 35275-2017(2)` - WebAdmin `signAction` 读取并传递 `tsType` / `tsUrl` / `sealType`,日志与 `lastTsInfo` 完整展示类型与地址;`signDo` JS 追加对应 FormData 字段;PDF 组国密模式(35275/38540)下选择非 SM2 证书与未选签章图片时给出明确中文报错 - **v1.4(2026-09-15)——签名专区修复与增强** - **微软代码签名(Authenticode)重写**:`SignTool.signExe` 改为手工 ASN.1 构造(对齐参考项目 `java-lening\微软RSA国密双签名带时间戳` 的合规点),修复旧实现(BC `CMSSignedDataGenerator` 自动构造)产物被 Windows 判 `0x80096010 / 签名无效` 的问题: - `SpcAttributeTypeAndOptionalValue.value` 直接 SEQUENCE(无 `[0]` 包装);`SpcPeImageData.file` 实际标签 `[0] EXPLICIT`;`SpcString` 空串 `[0] IMPLICIT 80 00`;`DigestInfo.digestAlgorithm.parameters` 带 `NULL`;eContent 直接 SEQUENCE(无 OCTET STRING 包装);messageDigest = SHA-256(SpcIndirectData **内容字节**,跳过 `30 xx` 头);证书表 8 字节对齐追加 `WIN_CERTIFICATE 0x0200/0x0002`;证书顺序根在前 - **自动时间戳**:签名后 `messageImprint = SHA-256(签名值)` → 请求本机 MAIN `/rfc3161`(`application/timestamp-query`,WEBPORT 读 `PKI/CONFIG.txt`)→ `TimeStampToken` 嵌入 unsignedAttrs(`1.3.6.1.4.1.311.3.3.1`);失败自动降级不影响签名,状态写入日志 - **实测(Windows 原生)**:`signtool verify /pa /v` → 0 错误、时间戳验证通过;`Get-AuthenticodeSignature` → **Valid**(证书链 ROOTCA→终端、时间戳 ROOTCA By Timstamp SHA256) - **已知限制**:中间 CA 若 EKU 仅含 serverAuth(不含代码签名),Windows Authenticode 链验证会拒绝该 CA 签发的证书用于代码签名(`signing certificate is not valid for the requested usage`)——根直接签发的代码签名证书不受影响;如需使用中间 CA 链,请重签 CA 时在 EKU 中加入代码签名或不设 EKU - SM2 主签名 EXE:签名与时间戳正常生成;Windows crypt32 白名单不含 SM2,原生验证不支持(与参考项目结论一致),可用本面板"签名验证"做链内验证 - **PDF 可见签章**:PDF 分组新增"签章图片(可选,png/jpg/bmp/gif)"选择框;采用参考项目 `SignatureOptions.setVisualSignature` 外观文档模式(独立外观 PDF 内放置带 Rect+AP 的 widget,PDFBox 将 Rect/外观搬至主文档第 1 页右下角,最大 150×80 等比缩放),**避免 addSignature 后改动文档破坏 ByteRange**(替代早期 PDSignatureField 手动方式,后者报 `signature reserve byte range has been changed`);不选签章则不可见签名 - 实测:`test.pdf`(5 页)带签章签名 → `CMS 签名验证通过`、文档摘要比对通过、证书链通过,第 1 页右下角签章渲染可见;无签章 PDF 签名回归通过;SM2 证书 EXE 签名回归通过 - **v1.3(2026-09-15)** - WebAdmin 新增「签名专区」页面(调试专区下方):两个分组均支持选择文件 + 下拉式证书选择(自动按 EKU 筛选、悬停显示证书简介) - **微软代码签名(Authenticode)**:对 exe/dll/sys 等 PE 文件按 Microsoft PE/COFF 规范嵌入证书表(WIN_CERTIFICATE 0x0200/0x0002 + CMS SignedData,contentType=spcIndirectDataContext,SpcIndirectDataContent 内含 Authenticode 文件哈希:跳过 Checksum/Security Directory/证书表区域),支持 RSA/ECC/SM2(SM2 走 GB/T 32918 含 ZA,摘要 SM3;RSA/ECC 摘要 SHA256);验证输出 CMS 签名、文件哈希比对、证书链逐级验证(签名证书 → 颁发者 → 自签根)三要素 - **PDF 文档签名**:标准 PKCS#7 detached(SubFilter adbe.pkcs7.detached),基于 PDFBox 3.0.4 外部签名两段式(saveIncrementalForExternalSigning)增量保存,ByteRange 由 PDFBox 维护;验证时以 ByteRange 区间数据作为 detached 内容构造 CMS 后验签(否则 BC 按空内容计算摘要导致 mismatch),同样支持 RSA/ECC/SM2 - 新增 `SignTool.java`(签名/验证核心,约 35KB):按 CERTS.txt 台账加载证书(R→ROOT、C→CA、E→CERT)与私钥(E→KEY),按颁发者关系加载完整证书链;EKU 筛选集合:代码签名 `1.3.6.1.5.5.7.3.3` / 内核模式 `1.3.6.1.4.1.311.61.1.1` / 驱动验证 `1.3.6.1.4.1.311.10.3.5` / 系统组件 `1.3.6.1.4.1.311.10.3.6` / OEM `1.3.6.1.4.1.311.10.3.7` / 内嵌 `1.3.6.1.4.1.311.10.3.8`,文档签名 `1.3.6.1.4.1.311.10.3.12` / 通用文档签名 `1.3.6.1.5.5.7.3.36`(代码签名证书亦可签 PDF) - WebAdmin 新增 API:`GET /api/signcerts?kind=exe|pdf`(可签名证书列表)、`POST /api/sign/exe`、`POST /api/sign/pdf`(multipart 上传文件 + serial,返回签名结果 + Base64 下载)、`POST /api/verify/exe`、`POST /api/verify/pdf`;所有签名/验证操作写入 logs.log - 依赖:`lib/pdfbox-app-3.0.4.jar`(PDFBox 3.0.4,从参考项目引入);`build.cmd` / `build.sh` 编译与运行 classpath 均加入 `lib\*`(`-cp "BC185\*;lib\*"` / `"out;BC185\*;lib\*"`) - 实测(真实样本):`CERTVERIFY.exe`(9.8MB)Authenticode 签名/验证三要素全部通过;`test.pdf`(5 页)与 SM2 证书 PDF 签名/验证全部通过(SM3 摘要、SM3withSM2 含 ZA 验签成功) - **v1.2(2026-09-14)** - WebAdmin 新增「调试专区」页面:两个分组均按标准协议构造请求并 POST 本机 MAIN,日志逐步展示 - **模拟 OCSP 请求(RFC 6960)**:按 CertID(issuerNameHash/issuerKeyHash 基于真正颁发者证书计算,哈希算法可选 SHA1/SHA256/SHA384/SHA512/SM3)+ Nonce 扩展构造请求 → 解析响应(responseStatus、responderID、证书状态/吊销原因时间、thisUpdate/nextUpdate、Nonce 回显比对)→ 用响应者证书验证签名 - **模拟时间戳请求(RFC 3161)**:随机 32 字节数据 → 按所选摘要算法(SHA1/SHA256/SHA384/SHA512/SM3)计算 messageImprint → 构造 TimeStampReq(Nonce + certReq)→ 解析 TimeStampResp(status、TimeStampToken 序列号/genTime/policy、messageImprint 回显比对)→ 用签名者证书验证 token 签名;MAIN 返回非 200 时显示真实错误与所需 TSA 证书提示 - OCSP 服务端行为(RFC 6960):解析请求哈希算法 OID 与序列号 → 按 `颁发者序列号_ocsp.crt/.key` 加载响应者密钥对;RSA/ECC 按请求哈希算法(sha1/256/384/512,缺省 sha1)签名,SM2 固定 SM3 签名(`MAIN.handleOcsp` + `OcspUtil`,此前已实现,本轮以调试专区实测 Nonce 回显与签名验证通过) - **v1.1(2026-09-13)** - 修复 SM2 分支 OCSP 吊销响应的 DER 编码错误:`CertStatus.revoked` 由 EXPLICIT 改为 RFC 6960 规定的 `[1] IMPLICIT RevokedInfo`(`OcspUtil.createResponseSm2`)。此前 openssl 解析报 `Field=revocationTime ... wrong tag`、BC 报 `illegal object in getInstance`,表现为 SM2 已吊销证书(含重复吊销/修改吊销原因)验证时输出 `OCSP无法检查吊销状态 | 上报失败~`;修复后 openssl / BC 均能正确解析出吊销时间与原因 - `build.cmd` / `build.sh` 支持编号参数:`build.cmd 1` ~ `build.cmd 8`,编译成功后自动运行对应编号的启动命令(编译失败则不运行;编号 4/5/7/8 为脚本内置固定示例参数) - WebAdmin 证书库美化:类型中文显示(R=根证书 / C=CA证书 / E=终端证书)、状态三色(V 正常绿 / R 已吊销深红 / N 未知橙)、颁发者序列号悬停悬浮窗;每行新增「验证」按钮(弹窗三态:正常/已吊销/未知,对应绿/红/橙配色标题)与「操作」按钮(吊销证书 / 续签证书 / 密钥更换分组,吊销支持下拉选择 + 证书简要信息) - `CertVerify` 证书策略识别补充 TSA 证书 OID `2.23.140.1.4.2`