You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何使用x509包验证EXE应用程序的数字签名?解决解析EXE时Block为Nil的报错问题

你的问题很典型——这段代码只能处理单独的PEM格式证书文件,而Windows EXE的数字签名是嵌入在PE文件结构里的Authenticode签名,本质是一个PKCS#7格式的签名块,不能直接用pem.Decode解析。下面给你两种可行的解决方案:

方案一:手动提取PKCS#7签名并验证(纯Go实现)

要实现这个,你需要先从PE文件中定位并提取Authenticode的PKCS#7数据,再用crypto/x509解析验证。可以借助轻量的PE解析库简化操作,比如github.com/saferwall/pe(先通过go get安装)。

示例代码

package main

import (
	"crypto/x509"
	"fmt"
	"os"

	"github.com/saferwall/pe"
)

func main() {
	filename := "target.exe"
	// 解析PE文件并加载证书信息
	peFile, err := pe.New(filename, pe.OptionParseCertificates)
	if err != nil {
		fmt.Printf("解析PE文件失败: %v\n", err)
		return
	}
	defer peFile.Close()

	// 检查是否存在数字签名
	if len(peFile.Certificates) == 0 {
		fmt.Println("该EXE文件未包含数字签名")
		return
	}
	// 提取PKCS#7格式的签名数据
	pkcs7Data := peFile.Certificates[0].Data

	// 解析PKCS#7数据
	p7, err := x509.ParsePKCS7(pkcs7Data)
	if err != nil {
		fmt.Printf("解析PKCS#7数据失败: %v\n", err)
		return
	}

	// 获取签名证书
	if len(p7.Certificates) == 0 {
		fmt.Println("签名数据中未找到证书")
		return
	}
	signingCert := p7.Certificates[0]
	fmt.Printf("签名证书主题: %s\n", signingCert.Subject)

	// 验证证书有效性(基础验证,完整验证需补充哈希匹配、链信任等逻辑)
	rootCerts, err := x509.SystemCertPool()
	if err != nil {
		fmt.Printf("获取系统根证书池失败: %v\n", err)
		return
	}
	opts := x509.VerifyOptions{Roots: rootCerts}
	if _, err = signingCert.Verify(opts); err != nil {
		fmt.Printf("证书验证失败: %v\n", err)
	} else {
		fmt.Println("证书有效")
	}
}

注意事项

  • 完整的Authenticode验证需要额外检查签名哈希与EXE原始内容的匹配度(需排除签名块本身),这部分逻辑可以参考PE文件格式规范实现。
  • 证书链验证建议使用系统根证书池,确保验证结果符合系统信任逻辑。

方案二:调用Windows系统API验证(更可靠)

如果你的程序仅在Windows环境运行,直接调用系统的WinVerifyTrust API是最优选择——它会自动处理签名哈希匹配、证书链信任、吊销检查等所有细节,结果更权威。

示例代码

package main

import (
	"fmt"
	"syscall"
	"unsafe"
)

var (
	advapi32         = syscall.NewLazyDLL("advapi32.dll")
	winVerifyTrust   = advapi32.NewProc("WinVerifyTrust")
)

const (
	WTD_CHOICE_FILE     = 1
	WTD_ACTION_VERIFY   = 0
	WTD_UICONTEXT_NONE  = 2
)

type GUID struct {
	Data1 uint32
	Data2 uint16
	Data3 uint16
	Data4 [8]byte
}

type WINTRUST_FILE_INFO struct {
	cbStruct       uint32
	pcwszFilePath  *uint16
	hFile          uintptr
	pgKnownSubject *GUID
}

type WINTRUST_DATA struct {
	cbStruct              uint32
	pPolicyCallbackData   uintptr
	pSIPClientData        uintptr
	dwUIChoice            uint32
	fdwRevocationChecks   uint32
	dwUnionChoice         uint32
	pFileInfo             *WINTRUST_FILE_INFO
	pCatalogInfo          uintptr
	pSIPClientData2       uintptr
	dwStateAction         uint32
	hWVTStateData         uintptr
	pwszURLReference      uintptr
	dwProvFlags           uint32
	dwUIContext           uint32
	pSignatureSettings    uintptr
}

func main() {
	filename := "target.exe"
	wFilename, err := syscall.UTF16PtrFromString(filename)
	if err != nil {
		fmt.Printf("转换文件名失败: %v\n", err)
		return
	}

	// 构造文件信任验证信息
	fileInfo := WINTRUST_FILE_INFO{
		cbStruct:      uint32(unsafe.Sizeof(WINTRUST_FILE_INFO{})),
		pcwszFilePath: wFilename,
	}

	trustData := WINTRUST_DATA{
		cbStruct:      uint32(unsafe.Sizeof(WINTRUST_DATA{})),
		dwUIChoice:    WTD_UICONTEXT_NONE, // 不显示UI
		dwUnionChoice: WTD_CHOICE_FILE,
		pFileInfo:     &fileInfo,
		dwStateAction: WTD_ACTION_VERIFY,
	}

	// 定义WinVerifyTrust的验证GUID
	var wintrustActionVerify GUID = GUID{
		Data1: 0xaac56b,
		Data2: 0xcd44,
		Data3: 0x11d0,
		Data4: [8]byte{0x8c, 0xc2, 0x00, 0xc0, 0x4f, 0xc2, 0x95, 0xee},
	}

	// 调用系统API验证签名
	ret, _, err := winVerifyTrust.Call(
		0,
		uintptr(unsafe.Pointer(&wintrustActionVerify)),
		uintptr(unsafe.Pointer(&trustData)),
	)

	if ret == 0 {
		fmt.Println("EXE数字签名验证通过")
	} else {
		fmt.Printf("签名验证失败: %v (错误码: %d)\n", err, ret)
	}
}

注意事项

  • 该代码仅适用于Windows平台,依赖系统的advapi32.dll
  • WTD_UICONTEXT_NONE参数会让验证过程静默完成,不会弹出系统提示框。

总结

  • 若需跨平台支持,选择方案一,但需自行补充完整的签名验证逻辑。
  • 若仅在Windows运行,方案二更简单可靠,直接复用系统成熟的验证机制。

内容的提问来源于stack exchange,提问作者Developer

火山引擎 最新活动