返回提交历史
Modified
.github/PUBLISHING.md
+9
-9
Modified
.github/workflows/publish-packages.yml
+2
-5
Modified
.github/workflows/validate.yml
+12
-13
Modified
README.md
+1
-1
Modified
driver/SIGNING.md
+5
-3
Added
driver/Test-Payload.ps1
+65
-0
XFEstudio/XFEExtension.NetCore.InputSimulator
切换 CI/CD 仅复用已签名驱动并简化流程
CI/CD 工作流和文档全面切换为直接复用仓库中已签名的 driver/payload/win-x64 目录,不再在 CI 上编译、签名或准备原生驱动。新增 driver/Test-Payload.ps1 校验脚本,确保驱动负载完整性、哈希、清单和证书有效性,CI 仅处理已验证包。文档同步更新,runner 仅需 Windows、PowerShell 7 和 .NET 10 SDK,无需 C++ Build Tools、WDK 或证书私钥。开发者如需修改驱动,需本地签名后提交。NuGet 包默认加 -driver-local 后缀,公共流程可发自签名包但不提升信任。整体提升安全性、可复现性和简化度。
e9061ca
代码差异
6 个文件
+94
-31
@@ -1,26 +1,26 @@
1
1
# 构建与发布
2
2
3
工作流参考 `XFEExtension.NetCore.ServerInteractive` 的 `Validate` 和 `Publish Packages`,使用 Windows runner 和 .NET 10 SDK。输入模拟库与测试项目均以 `net10.0` 为目标框架。
3
工作流参考 `XFEExtension.NetCore.ServerInteractive` 的 `Validate` 和 `Publish Packages`,使用 Windows runner、PowerShell 7 和 .NET 10 SDK。输入模拟库与测试项目均以 `net10.0` 为目标框架。两个工作流都复用 Git 中的已签名驱动,不需要 Visual Studio、C++ Build Tools、vswhere、WDK 或签名私钥;自建运行机也无需安装这些原生构建工具。
4
4
5
5
## 自动验证
6
6
7
7
`validate.yml` 在以下情况运行:向 `master` / `main` 推送、创建或更新 Pull Request、从 Actions 页面手动触发。
8
8
9
先还原固定版本的微软 WDK/SDK NuGet 工具,编译项目内的 UMDF HID 驱动和安装器,运行原生报告及会话测试、INF 校验及目录生成,再内嵌开发负载、构建 .NET 10 项目、运行 `XFEExtension.NetCore.XUnit` 测试并打包主库。测试使用 `dotnet run`,返回失败退出码或未发现测试用例时会阻止后续打包。工作流保存 JSON / JUnit XML、未签名驱动及开发 `.nupkg`,不会发布包。
9
先执行 `driver/Test-Payload.ps1` 检查已提交的完整驱动负载及元数据,再直接构建 .NET 10 项目、运行 `XFEExtension.NetCore.XUnit` 测试并打包主库。测试使用 `dotnet run`,返回失败退出码或未发现测试用例时会阻止后续打包。工作流保存 JSON / JUnit XML、已签名驱动及 `.nupkg`,不会发布包。
10
10
11
`unsigned-driver` 产物包含 UMDF DLL、INF、CAT、PDB 及安装器。CI 不使用签名私钥、不生成或提交微软审核 CAB。签名及后续处理见 [驱动签名说明](../driver/SIGNING.md)。
11
`signed-driver-payload` 产物包含 Git 中的全部 7 个负载文件:UMDF DLL、INF、CAT、安装器、公开证书及两份元数据。CI 不重编译驱动、不生成或替换签名文件。签名及后续处理见 [驱动签名说明](../driver/SIGNING.md)。
12
12
13
开发 NuGet 包默认带 `-driver-dev` 后缀。完整驱动文件作为资源嵌入 DLL;显式安装接口会拒绝未签名负载。CI 不安装驱动或修改 runner 的证书信任与启动安全策略。
13
当前自签名负载在普通验证构建中默认生成 `-driver-local` NuGet 包。发布工作流使用标签或手动输入的版本,驱动在包内仍标记为 `self-signed`,不会因上传 NuGet.org 就获得公共证书信任。CI 不安装驱动或修改 runner 的证书信任与启动安全策略。
14
14
15
15
自动测试使用记录后端,不向 runner 的桌面发送真实输入。Windows CI 使用默认的 x64 .NET 主机;本地可按 README 中的命令额外运行 x86 测试。
16
16
17
17
## 发布凭据
18
18
19
发布前,当前 ref 必须包含 `driver/signed/win-x64` 下使用同一公共代码签名证书签好的 `XfeInputDriver.dll`、`XfeInput.cat`、`XfeInputSetup.exe`,以及 CAT 覆盖的 `XfeInput.inf`。工作流检查证书用途、非自签名、有效签名和 DLL/INF 目录成员哈希;不再检查项目 SYS 的内核签名,也不要求提交微软硬件审核。公钥证书从签名中提取并内嵌,私钥不得入库。
19
发布前,当前 ref 必须包含 `driver/payload/win-x64` 下的完整已签名负载。工作流检查 SHA-256、签名类型和时间戳声明、公开证书用途及有效期、JSON 与 MSBuild 元数据一致性;未签名、缺失或被改动的文件会使工作流失败。
20
20
21
当前 `-driver-local` 自签名包供用户明确授权信任后安装。维护者首次运行 `driver/New-SigningCertificate.ps1` 创建十年专用证书,再通过 `Build.ps1 -LocalSigning` 复用身份、签名并请求 RFC 3161 时间戳;也支持显式指定 `-LocalCertificateThumbprint '<指纹>'`。私钥不进入工作流或包。公共发布工作流不会将它误发布为公共受信任版本,也不会在 runner 上导入本地 Root 证书。
21
CI 的仓库完整性检查不替代 Authenticode 信任验证或 CAT 成员验证。维护者首次运行 `driver/New-SigningCertificate.ps1` 创建长期专用证书,再通过 `Build.ps1 -LocalSigning` 编译、运行原生测试、签名和校验,然后提交更新后的整套负载。也支持显式指定 `-LocalCertificateThumbprint '<指纹>'`。私钥不进入工作流或包;自签名证书不需要被 runner 信任。
22
22
23
签名是维护者的发布步骤。调用方只需要 DLL/NuGet,使用 `DriverDeployment.CheckDriver()` 检测设备,在显式调用 `InstallEmbeddedDriver()` 时由内嵌安装器申请管理员授权和所需的签名者信任。连接和输入均不会自动安装;无需自己签名、下载驱动或配置 WDK。`driver/signed/win-x64` 中只保存发行二进制,不保存私钥。
23
签名是维护者的发布步骤。调用方只需要 DLL/NuGet,使用 `DriverDeployment.CheckDriver()` 检测设备,在显式调用 `InstallEmbeddedDriver()` 时由内嵌安装器申请管理员授权和所需的签名者信任。连接和输入均不会自动安装;无需自己签名、下载驱动或配置 WDK。
24
24
25
25
在 GitHub 仓库的 Settings → Secrets and variables → Actions 中配置:
26
26
@@ -52,11 +52,11 @@
52
52
在仓库根目录执行:
53
53
54
54
```powershell
55
pwsh -File .\Build.ps1 -ReleasePackage
55
pwsh -File .\driver\Test-Payload.ps1
56
56
dotnet restore .\XFEExtension.NetCore.InputSimulator.sln
57
57
dotnet build .\XFEExtension.NetCore.InputSimulator.sln -c Release --no-restore -p:GeneratePackageOnBuild=false -p:ContinuousIntegrationBuild=true -warnaserror
58
58
dotnet run --project .\XFEExtension.NetCore.InputSimulator.Tests -c Release --no-build --no-restore -- --artifacts artifacts/tests
59
59
dotnet pack .\XFEExtension.NetCore.InputSimulator\XFEExtension.NetCore.InputSimulator.csproj -c Release --no-build --no-restore --output artifacts/packages -p:GeneratePackageOnBuild=false -p:ContinuousIntegrationBuild=true
60
60
```
61
61
62
仅进行开发验证时使用 `pwsh -File .\Build.ps1`,生成内嵌未签名驱动的开发包。程序集强名称签名与驱动包 Authenticode 签名是两回事;默认程序集不强名称签名,工作流不读取仓库历史 PFX 或其他私钥。
62
只有需要修改原生驱动时才运行 `pwsh -File .\Build.ps1`,此路径需要 C++ 环境,并生成内嵌未签名驱动的开发包;重新签名后才能提交负载。复用仓库驱动时使用上面的命令,不调用 `Build-Driver.ps1` 或 `Prepare-Payload.ps1`。程序集强名称签名与驱动包 Authenticode 签名是两回事;默认程序集不强名称签名,工作流不读取仓库历史 PFX 或其他私钥。
@@ -77,11 +77,8 @@ jobs:
77
77
throw 'Set the NUGET_API_KEY repository secret before publishing to NuGet.org.'
78
78
}
79
79
80
- name: Build native driver and test HID reports
81
run: ./driver/Build-Driver.ps1
82
83
- name: Verify and embed signed distribution driver
84
run: ./driver/Prepare-Payload.ps1 -PackageDirectory ./driver/signed/win-x64
80
- name: Verify committed signed driver payload integrity
81
run: ./driver/Test-Payload.ps1
85
82
86
83
- name: Restore
87
84
run: dotnet restore "$env:SOLUTION_PATH"
@@ -38,22 +38,21 @@ jobs:
38
38
with:
39
39
dotnet-version: ${{ env.DOTNET_VERSION }}
40
40
41
- name: Build UMDF driver and test HID reports and sessions
42
run: ./driver/Build-Driver.ps1
41
- name: Verify committed signed driver payload integrity
42
run: ./driver/Test-Payload.ps1
43
43
44
- name: Embed development driver payload
45
run: ./driver/Prepare-Payload.ps1 -AllowUnsigned
46
47
- name: Upload unsigned driver build
44
- name: Upload committed signed driver payload
48
45
uses: actions/upload-artifact@v4
49
46
with:
50
name: unsigned-driver
47
name: signed-driver-payload
51
48
path: |
52
artifacts/driver-umdf/*.dll
53
artifacts/driver-umdf/*.inf
54
artifacts/driver-umdf/*.cat
55
artifacts/driver-umdf/*.pdb
56
artifacts/driver-umdf/XfeInputSetup.exe
49
driver/payload/win-x64/XfeInputDriver.dll
50
driver/payload/win-x64/XfeInput.inf
51
driver/payload/win-x64/XfeInput.cat
52
driver/payload/win-x64/XfeInputSetup.exe
53
driver/payload/win-x64/publisher.cer
54
driver/payload/win-x64/payload.json
55
driver/payload/win-x64/payload.props
57
56
if-no-files-found: error
58
57
59
58
- name: Restore
@@ -83,6 +82,6 @@ jobs:
83
82
- name: Upload package artifacts
84
83
uses: actions/upload-artifact@v4
85
84
with:
86
name: development-packages-unsigned-driver
85
name: packages-signed-driver
87
86
path: ${{ env.PACKAGE_OUTPUT_DIR }}/*.nupkg
88
87
if-no-files-found: error
@@ -155,4 +155,4 @@ pwsh -File .\Publish-Local.ps1
155
155
156
156
不要分发私钥;请自行用密码加密备份到离线介质。时间戳不是永久安装保证,当前安装器仍会拒绝过期证书,需要在 2036 年期限前发布新证书签名的更新。已安装 30 天验证版时用 `--driver-install` 升级,普通连接不会自动替换兼容的旧驱动。完整步骤、信任范围和卸载方式见 [驱动签名与分发](driver/SIGNING.md)。
157
157
158
`Validate` 工作流构建 UMDF 驱动、验证原生测试和 C# 测试,并保存未签名驱动及开发 NuGet 包。`Publish Packages` 只接受 `driver/signed/win-x64` 中通过公共签名校验的发行负载;本地信任版通过本地命令分发。配置详见 [发布说明](.github/PUBLISHING.md)。
158
`Validate` 和 `Publish Packages` 工作流均直接复用 Git 中的 `driver/payload/win-x64`,检查文件 SHA-256、清单及公开证书元数据,再构建和测试 C# 项目、打包 NuGet;不运行原生编译或重新签名。运行机只需 Windows、PowerShell 7 和 .NET 10 SDK,不需要 C++ Build Tools、WDK 或证书私钥,也不会导入证书信任。修改原生代码时,维护者仍需在签名机执行原生构建、测试、签名及 CAT 校验后更新整套负载。配置详见 [发布说明](.github/PUBLISHING.md)。
@@ -70,11 +70,13 @@ Export-PfxCertificate -Cert $certificate -FilePath 'E:\PrivateBackup\InputSimula
70
70
71
71
路径按自己的离线介质修改,备份文件和密码分开保管;仓库已忽略 `*.pfx`。迁移机器时用 `Import-PfxCertificate` 将加密备份导入新用户的 `My` 存储,并恢复证书选择文件。缺失选择文件时也可直接指定原证书指纹构建。若私钥丢失且没有备份,将无法继续使用同一身份签名;若泄露,应停止分发该证书签名的包,更新签名身份并通知使用者移除旧信任。
72
72
73
## 公共发行及 CI
73
## 分发及 CI
74
74
75
当前公开发布工作流只接受在干净 Windows runner 上可验证、非自签名的代码签名发行包;不是要求微软审核项目自有内核驱动。将使用同一发布者证书签好的 DLL、INF、CAT、安装器放入 `driver/signed/win-x64`,执行 `Build.ps1 -ReleasePackage`。公开证书从签名中提取并内嵌,打包前检查签名、证书用途和目录成员哈希。默认开发 CI 仅产出 `-driver-dev` 未签名包,安装接口会拒绝该包。
75
验证和发布工作流直接复用 `driver/payload/win-x64` 中已提交的签名驱动包,不要求 runner 安装 C++ Build Tools、WDK 或签名私钥。`driver/Test-Payload.ps1` 检查完整文件集合、SHA-256、打包元数据及公开证书用途和有效期。这是仓库负载完整性检查,不是 Windows Authenticode 信任或 CAT 成员验证;后两项由维护者在签名机运行 `Prepare-Payload.ps1` 时完成。CI 不导入发布者证书、不修改系统信任、不安装驱动。
76
76
77
自签名信任版本目前通过上面的本地命令构建分发,不由公共发布工作流上传。长期自签名发行需要维护证书、更新通道和信任说明;不能将“驱动能在本机运行”等同于“任意机器或游戏都会接受”。
77
当前自签名负载也可由 `Publish Packages` 发布到 NuGet.org / GitHub Packages;上传软件包不改变驱动的 `self-signed` 类型,使用者首次安装仍需确认发布者信任。本地默认打包附加 `-driver-local` 后缀,工作流显式按标签或手动输入指定 NuGet 版本。更新原生代码后,先在签名机重新构建、运行原生测试、签名及验证,再提交全部 7 个负载文件。长期自签名发行需要维护证书、更新通道和信任说明。
78
79
另行使用公共代码证书的维护者仍可将签好的原生文件放入 `driver/signed/win-x64`,运行 `Build.ps1 -ReleasePackage`;该原生构建路径需要 C++ 环境,CI 复用已提交负载时不调用它。
78
80
79
81
## 卸载
80
82
@@ -0,0 +1,65 @@
1
[CmdletBinding()]
2
param([string]$PackageDirectory = (Join-Path $PSScriptRoot 'payload\win-x64'))
3
$ErrorActionPreference = 'Stop'
4
5
# Reuse the bytes verified by Prepare-Payload.ps1 on the signing machine.
6
# This is a repository-integrity check, not Windows Authenticode trust validation.
7
# No SDK, WDK, signing key, certificate-store change or driver installation is needed.
8
$source = (Resolve-Path -LiteralPath $PackageDirectory).Path
9
$files = @('XfeInputDriver.dll', 'XfeInput.inf', 'XfeInput.cat', 'XfeInputSetup.exe', 'publisher.cer')
10
foreach ($file in ($files + @('payload.json', 'payload.props'))) {
11
if (!(Test-Path -LiteralPath (Join-Path $source $file) -PathType Leaf)) {
12
throw "Missing committed driver payload: $file. Restore the complete signed driver/payload/win-x64 directory from Git."
13
}
14
}
15
16
$manifest = Get-Content -LiteralPath (Join-Path $source 'payload.json') -Raw | ConvertFrom-Json
17
if ($manifest.protocol -ne 1 -or $manifest.signed -isnot [bool] -or !$manifest.signed -or
18
$manifest.signingKind -cnotin @('self-signed', 'trusted-publisher') -or
19
$manifest.timestamped -isnot [bool] -or !$manifest.timestamped) {
20
throw 'The committed driver payload must be signed, timestamped and use protocol 1. Prepare it on the signing machine before committing.'
21
}
22
$entries = @($manifest.files.PSObject.Properties)
23
if ($entries.Count -ne $files.Count -or @($entries.Name | Where-Object { $_ -cnotin $files }).Count) {
24
throw 'The driver manifest must contain exactly the five expected payload files.'
25
}
26
foreach ($file in $files) {
27
$expected = $manifest.files.$file
28
if ($expected -isnot [string] -or $expected -cnotmatch '^[A-Fa-f0-9]{64}$' -or
29
(Get-FileHash -LiteralPath (Join-Path $source $file) -Algorithm SHA256).Hash -ne $expected) {
30
throw "Driver payload SHA-256 mismatch: $file. Do not modify signed files or normalize the INF line endings."
31
}
32
}
33
34
[xml]$properties = Get-Content -LiteralPath (Join-Path $source 'payload.props') -Raw
35
if ($properties.Project.PropertyGroup.DriverPayloadSigned -cne 'true' -or
36
$properties.Project.PropertyGroup.DriverPayloadSigningKind -cne $manifest.signingKind) {
37
throw 'payload.props does not match the signed driver manifest.'
38
}
39
40
$certificate = [Security.Cryptography.X509Certificates.X509Certificate2]::new([IO.File]::ReadAllBytes((Join-Path $source 'publisher.cer')))
41
try {
42
if ($certificate.HasPrivateKey) { throw 'Only the public publisher certificate may be committed.' }
43
$now = [DateTime]::UtcNow
44
if ($certificate.NotBefore.ToUniversalTime() -gt $now -or $certificate.NotAfter.ToUniversalTime() -le $now) {
45
throw 'The publisher certificate is not currently valid. Refresh the signed driver package on the signing machine.'
46
}
47
# ConvertFrom-Json returns a string on older PowerShell and a DateTime on newer versions.
48
if (([DateTimeOffset]$manifest.certificateExpires).UtcDateTime -ne $certificate.NotAfter.ToUniversalTime()) {
49
throw 'The certificate expiry in payload.json does not match publisher.cer.'
50
}
51
$usage = @(($certificate.Extensions | Where-Object { $_.Oid.Value -eq '2.5.29.37' }).EnhancedKeyUsages | ForEach-Object { $_.Value })
52
$constraints = $certificate.Extensions | Where-Object { $_.Oid.Value -eq '2.5.29.19' }
53
if ($usage.Count -ne 1 -or $usage[0] -ne '1.3.6.1.5.5.7.3.3' -or !$constraints -or $constraints.CertificateAuthority) {
54
throw 'The publisher certificate must be non-CA and restricted to code signing.'
55
}
56
$selfSigned = [Convert]::ToBase64String($certificate.SubjectName.RawData) -eq [Convert]::ToBase64String($certificate.IssuerName.RawData)
57
if ($selfSigned -ne ($manifest.signingKind -eq 'self-signed')) {
58
throw 'The publisher certificate does not match the manifest signing kind.'
59
}
60
} finally {
61
$certificate.Dispose()
62
}
63
64
Write-Host "Committed driver payload integrity verified ($($manifest.signingKind)): $source"
65
Write-Host 'Reusing the signed package without native compilation, signing or certificate trust changes.'