go版本增加软件签名

This commit is contained in:
maxf committed 2026-09-28 10:15:35 +08:00
1 parent c4b5a4ae41
commit d076291a62
4 files changed
+349 -52

No files matched your search

+5 -1
View File
@@ -10,4 +10,8 @@
/frontend/node_modules/ /frontend/node_modules/
# 测试二进制 # 测试二进制
*.test *.test
# 代码签名证书与工具(私钥材料,绝不入库)
/certs/
/tools/
+224
View File
@@ -0,0 +1,224 @@
# Excel 关键字搜索工具 —— 代码签名说明
Windows 发布版可执行文件(`.exe`)的 **Authenticode 代码签名** 与 **时间戳** 全流程说明。
用于解决打包产物被 Symantec 等杀软误报(如 `Heur.AdvML.B`)的问题,也是正式对外分发的标准动作。
> 适用对象:`cmd/desktop`、`cmd/webui`、`cmd/searcher` 三个入口编译出的全部 exe。
---
## 一、为什么需要签名
| 现象 | 原因 | 签名的作用 |
|---|---|---|
| 杀软误报 `Heur.AdvML.B` | 无签名 + 符号剥离(`-s -w`)的结构特征,被启发式 ML 视为可疑 | 带 Authenticode 签名的 exe 结构显著不同,评分大幅下降 |
| 无签名 exe 被拦截/隔离 | 系统信任模型默认不信任"未知来源"程序 | 签名建立发布者身份,提高系统信任度 |
**自签名证书的现实效果**:自签名证书**不链到公共信任根**,Symantec/Windows 不会"信任"该签名;但签名块本身已让 exe 脱离"无签名"形态,绝大多数启发式 ML 误报可消除。若要彻底消除全部杀软误报并显示绿色签名,需购买公共 OV/EV 证书(见「七、升级到公共证书」)。
---
## 二、相关文件与目录结构
```
go/
├── sign.bat # 一键签名脚本(纳入版本控制)
├── certs/ # 签名证书(已 gitignore,私钥材料,绝不入库/分发)
│ ├── codesign.crt # 自签名证书(公开部分)
│ ├── codesign.key # 私钥(⚠️ 最高机密)
│ └── codesign.pfx # 证书+私钥打包(签名工具实际使用)
└── tools/ # 签名工具(已 gitignore,可重新下载)
└── osslsigncode/ # osslsigncode 便携版(含依赖 DLL)
└── osslsigncode.exe
```
> `certs/` 与 `tools/` 已在 `go/.gitignore` 中排除,**不会**被提交到版本库,也不会进入发布包。
### 工具获取(首次安装 / tools/ 被清理后重装)
`tools/osslsigncode/` 是便携版,不随版本库分发。在 Git Bash 中执行:
```bash
cd go
mkdir -p tools && cd tools
curl -sL -o osslsigncode.zip \
https://github.com/mtrojnar/osslsigncode/releases/download/2.14/osslsigncode-2.14-windows-x64-mingw.zip
unzip -o osslsigncode.zip
# zip 解压后 exe 在 bin/ 子目录,移动到 sign.bat 引用的布局
mkdir -p osslsigncode
mv -f bin/* osslsigncode/
rm -rf bin share osslsigncode.zip
# 验证
./osslsigncode/osslsigncode.exe --help | head -3
```
> ⚠️ zip 内含 `bin/osslsigncode.exe` + 依赖 DLL(`libcrypto-3-x64.dll`、`libssl-3-x64.dll`、`zlib1.dll`),必须与 exe 同目录,缺 DLL 会运行失败。
---
## 三、签发自签名代码签名证书(首次一次)
> 前置:Git Bash(自带 OpenSSL ≥ 3.1)。以下命令在 Git Bash 中执行。
> ⚠️ **MSYS 路径转换坑**:`-subj` 的 `/C=CN/...` 会被 Git Bash 误判为 POSIX 路径,
> 必须加 `MSYS_NO_PATHCONV=1` 前缀,否则报 `subject name is expected to be in the format...`。
```bash
cd go
mkdir -p certs
MSYS_NO_PATHCONV=1 openssl req -x509 -newkey rsa:2048 -sha256 -nodes \
-keyout certs/codesign.key \
-out certs/codesign.crt \
-days 3650 \
-subj "/C=CN/ST=Sichuan/L=Chengdu/O=Excel Keyword Search Tool/OU=Release/CN=Excel Keyword Search Tool" \
-addext "keyUsage=digitalSignature, nonRepudiation" \
-addext "extendedKeyUsage=codeSigning" \
-addext "basicConstraints=critical,CA:FALSE"
```
参数说明:
- `-x509`:直接自签(不经过 CA),`-nodes`:私钥不加密(本地文件使用,配合 `-pass` 保护 pfx)
- `-addext extendedKeyUsage=codeSigning`:声明**代码签名**用途(必填,杀软/系统校验此项)
- `-basicConstraints=CA:FALSE`:明确非 CA,防被滥用
- `-days 3650`:10 年有效期(正式发布建议 ≥ 1 年)
### 导出 PFX(签名工具实际使用)
```bash
openssl pkcs12 -export \
-out certs/codesign.pfx \
-inkey certs/codesign.key \
-in certs/codesign.crt \
-passout pass:ExcelSearch2026 \
-name "ExcelKeywordSearchRelease"
```
> `pass:ExcelSearch2026` 是**默认密码**(脚本内置)。正式环境请更换,并通过 `SIGN_PASSWORD` 环境变量注入(见下)。
### 验证证书
```bash
openssl x509 -in certs/codesign.crt -noout -subject -issuer -dates -text | grep -A3 "Extended Key Usage"
# 应输出:Code Signing
```
---
## 四、签名可执行文件
### 方式一:一键脚本 `sign.bat`(推荐)
在 `go/` 目录下执行(cmd 或双击):
```bat
sign.bat
```
- 自动扫描 `bin\*.exe`,逐个签名 + 时间戳后**原地覆盖**。
- 输出:`[sign] SIGNED : excel-search-desktop.exe` 等。
- 指定密码:
```bat
set SIGN_PASSWORD=你的强密码
sign.bat
```
### 方式二:集成到 `build.bat`(发布流程)
`build.bat` 已内置可选签名步骤,构建后自动签名:
```bat
set VERSION=v1.2.3
set SIGN=1
build.bat
```
构建三个 exe → 自动调用 `sign.bat` 全部签名 + 时间戳。
### 方式三:手动调用 osslsigncode(Git Bash)
```bash
./tools/osslsigncode/osslsigncode.exe sign \
-pkcs12 certs/codesign.pfx \
-pass "ExcelSearch2026" \
-n "Excel Keyword Search Tool" \
-t http://timestamp.digicert.com \
-in bin/excel-searcher.exe \
-out bin/excel-searcher.signed.exe
mv -f bin/excel-searcher.signed.exe bin/excel-searcher.exe
```
> osslsigncode **不允许输入输出同一文件**,必须先生成临时文件再覆盖。
---
## 五、验证签名
### Git Bash(osslsigncode)
```bash
./tools/osslsigncode/osslsigncode.exe verify bin/excel-searcher.exe
# 期望:Message digest 一致、Signer's certificate 正确、含时间戳信息
```
### PowerShell(系统级,最权威)
```powershell
Get-AuthenticodeSignature .\bin\excel-searcher.exe
# Status: Valid
# SignerCertificate: CN=Excel Keyword Search Tool, ...
# TimeStamperCertificate: CN=DigiCert Timestamp Responder, ...
```
---
## 六、时间戳说明
签名时使用 `-t http://timestamp.digicert.com`(RFC 3161 时间戳服务器):
- **作用**:记录签名发生的准确时刻,即使未来证书过期,**历史签名依然有效**。
- **必须加**:不加时间戳的签名会在证书到期后失效。
- 备选时间戳服务器:`http://timestamp.sectigo.com`、`http://timestamp.apple.com`。
---
## 七、升级到公共证书(彻底消除误报)
自签名证书只解决"结构误报",若要杀软完全信任并显示有效签名:
1. 向 **Sectigo / DigiCert / 阿里云 / 腾讯云** 等购买 **OV/EV 代码签名证书**(EV 更优,需硬件 Key,签名前插 U 盾)。
2. 拿到 `.pfx` 或证书+私钥后,替换 `certs/codesign.pfx`。
3. 其余流程完全一致(`sign.bat` / `build.bat` / 验证方式不变)。
---
## 八、安全警示
- `codesign.key` 与 `codesign.pfx` 是**私钥材料**,一旦泄露,他人可冒名签名恶意程序。
- **绝不要**提交进 git、发送给他人或放入发布包。
- 建议:
- `certs/` 目录仅保留在受控构建机。
- 密码定期更换(通过 `SIGN_PASSWORD` 注入,勿写死在文档/脚本明文)。
- 发布包只包含**签名后的 exe**,不含任何 `certs/`、`tools/` 内容。
---
## 九、常见问题
**Q:签名后 Symantec 仍然拦截?**
- 自签名证书不被 Symantec 信任,部分机器仍可能报"未知发布者"。属预期;误报率已大幅下降。
- 彻底解决走「七、升级到公共证书」,并向 Symantec 提交白名单申诉:
<https://symantec.brightcloud.com/tools/submiturl>
**Q:`subject name is expected to be in the format...`?**
Git Bash 把 `-subj` 的 `/C=CN` 当路径转换了。加 `MSYS_NO_PATHCONV=1` 前缀重试。
**Q:`Unable to load provider: legacy` 警告?**
osslsigncode 内置 OpenSSL 的提示,不影响 SHA256 签名结果,可忽略。
**Q:报 `Failed to add store lookup file`?**
验证命令的证书链查找提示,只要 `Message digest` 一致、`Signer's certificate` 正确即为签名有效,可忽略。
+64 -51
View File
@@ -1,51 +1,64 @@
@echo off @echo off
REM Excel Keyword Search Tool - Windows build script REM Excel Keyword Search Tool - Windows build script
REM Version can be overridden via the VERSION env var, defaults to "dev". REM Version can be overridden via the VERSION env var, defaults to "dev".
setlocal enabledelayedexpansion setlocal enabledelayedexpansion
if "%VERSION%"=="" set VERSION=dev if "%VERSION%"=="" set VERSION=dev
REM ---- Locate the go command ---- REM ---- Locate the go command ----
where go >nul 2>nul where go >nul 2>nul
if not errorlevel 1 goto go_ready if not errorlevel 1 goto go_ready
REM go is not on PATH. Locate the real go.exe under mise installs REM go is not on PATH. Locate the real go.exe under mise installs
REM (points directly at the toolchain, avoiding the mise shim stub which REM (points directly at the toolchain, avoiding the mise shim stub which
REM itself requires the mise binary to be on PATH). REM itself requires the mise binary to be on PATH).
set "GO_BIN=" set "GO_BIN="
for /d %%d in ("%APPDATA%\CherryStudio\Toolchain\mise\installs\go\*") do ( for /d %%d in ("%APPDATA%\CherryStudio\Toolchain\mise\installs\go\*") do (
if not defined GO_BIN if exist "%%d\bin\go.exe" set "GO_BIN=%%d\bin" if not defined GO_BIN if exist "%%d\bin\go.exe" set "GO_BIN=%%d\bin"
) )
if defined GO_BIN ( if defined GO_BIN (
set "PATH=!GO_BIN!;!PATH!" set "PATH=!GO_BIN!;!PATH!"
echo [build] Go toolchain auto-detected at !GO_BIN! echo [build] Go toolchain auto-detected at !GO_BIN!
goto go_ready goto go_ready
) )
:no_go :no_go
echo [build] ERROR: go command not found. echo [build] ERROR: go command not found.
echo Install Go from https://go.dev/dl/ and add it to PATH, echo Install Go from https://go.dev/dl/ and add it to PATH,
echo or ensure the mise-managed Go toolchain is installed. echo or ensure the mise-managed Go toolchain is installed.
pause pause
exit /b 1 exit /b 1
:go_ready :go_ready
if not exist bin mkdir bin if not exist bin mkdir bin
echo [build] Building version %VERSION% ... echo [build] Building version %VERSION% ...
go version go version
go build -trimpath -ldflags "-s -w -H=windowsgui -X main.version=%VERSION%" -o bin\excel-search-desktop.exe .\cmd\desktop || goto failed go build -trimpath -ldflags "-s -w -H=windowsgui -X main.version=%VERSION%" -o bin\excel-search-desktop.exe .\cmd\desktop || goto failed
go build -trimpath -ldflags "-s -w -H=windowsgui -X main.version=%VERSION%" -o bin\excel-search-webui.exe .\cmd\webui || goto failed go build -trimpath -ldflags "-s -w -H=windowsgui -X main.version=%VERSION%" -o bin\excel-search-webui.exe .\cmd\webui || goto failed
go build -trimpath -ldflags "-s -w -X main.version=%VERSION%" -o bin\excel-searcher.exe .\cmd\searcher || goto failed go build -trimpath -ldflags "-s -w -X main.version=%VERSION%" -o bin\excel-searcher.exe .\cmd\searcher || goto failed
echo. REM ---- Optional code signing (release builds) ----
echo [build] Build succeeded (version %VERSION%), artifacts in bin\: REM Enable with: set SIGN=1 && build.bat
dir /b bin if /i "%SIGN%"=="1" (
pause echo.
exit /b 0 echo [build] Signing artifacts with code signing certificate ...
call sign.bat
:failed if errorlevel 1 (
echo. echo [build] Signing failed.
echo [build] Build failed. See the error messages above. pause
pause exit /b 1
exit /b 1 )
)
echo.
echo [build] Build succeeded (version %VERSION%), artifacts in bin\:
dir /b bin
pause
exit /b 0
:failed
echo.
echo [build] Build failed. See the error messages above.
pause
exit /b 1␍
+56
View File
@@ -0,0 +1,56 @@
@echo off
REM ============================================================
REM Excel Keyword Search Tool - code signing script
REM Signs every exe in bin\ with Authenticode + RFC3161 timestamp
REM (timestamp keeps signature valid after cert expiry).
REM
REM Usage:
REM sign.bat sign bin\*.exe with default pfx password
REM set SIGN_PASSWORD=xxx && sign.bat use custom pfx password
REM
REM Prerequisites:
REM certs\codesign.pfx must exist (private key material, gitignored)
REM tools\osslsigncode\ portable build must be present
REM NOTE: keep this file pure ASCII to avoid cmd codepage garbling.
REM ============================================================
setlocal enabledelayedexpansion
set "ROOT=%~dp0"
set "PFX=%ROOT%certs\codesign.pfx"
set "SIGNEXE=%ROOT%tools\osslsigncode\osslsigncode.exe"
if "%SIGN_PASSWORD%"=="" set "SIGN_PASSWORD=ExcelSearch2026"
set "TS_SERVER=http://timestamp.digicert.com"
set "APPNAME=Excel Keyword Search Tool"
if not exist "%PFX%" (
echo [sign] ERROR: certificate not found at %PFX%
exit /b 1
)
if not exist "%SIGNEXE%" (
echo [sign] ERROR: osslsigncode not found at %SIGNEXE%
exit /b 1
)
set "COUNT=0"
set "FAILED=0"
for %%f in ("%ROOT%bin\*.exe") do (
if exist "%%f" (
"%SIGNEXE%" sign -pkcs12 "%PFX%" -pass "%SIGN_PASSWORD%" -n "%APPNAME%" -t "%TS_SERVER%" -in "%%f" -out "%%~dpnf.signed.exe" >nul 2>&1
if errorlevel 1 (
echo [sign] FAILED : %%~nxf
set /a FAILED+=1
) else (
move /y "%%~dpnf.signed.exe" "%%f" >nul 2>&1
echo [sign] SIGNED : %%~nxf
set /a COUNT+=1
)
)
)
echo.
if %FAILED% gtr 0 (
echo [sign] Done: %COUNT% signed, %FAILED% failed.
exit /b 1
)
echo [sign] Done: %COUNT% executable(s) signed and timestamped.
exit /b 0␍