Golang中如何验证数字签名的二进制文件
Golang中如何验证数字签名的二进制文件 你好。我是GoLang的新手,正在尝试弄清楚如何在Windows上验证已签名二进制文件(例如.ps1)上的x.509签名。我希望根据我提供的受信任证书(来自应用程序信任库)进行验证。同时,我还希望验证自签名应用以来,二进制文件本身是否未被修改。是否有原生的GoLang库可以实现这个功能?我已经研究过crypto库,但它看起来更多是针对SSL的,而不是签名验证。
谢谢!
1 回复
更多关于Golang中如何验证数字签名的二进制文件的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在Go中验证Windows二进制文件的数字签名可以使用crypto/x509和golang.org/x/sys/windows包。以下示例演示如何验证PE文件的签名:
package main
import (
"crypto/x509"
"encoding/asn1"
"encoding/binary"
"errors"
"fmt"
"io"
"os"
"unsafe"
"golang.org/x/sys/windows"
)
// Windows API常量
const (
WTD_CHOICE_FILE = 1
WTD_REVOKE_NONE = 0
WTD_UI_NONE = 2
)
// WINTRUST_DATA结构体
type WinTrustData struct {
Size uint32
PolicyCallback uintptr
SIPCallback uintptr
UIChoice uint32
RevocationFlags uint32
UnionChoice uint32
FileOrCatalog uintptr
StateAction uint32
StateData uintptr
URLReference uintptr
ProviderFlags uint32
UIContext uint32
}
// WINTRUST_FILE_INFO结构体
type WinTrustFileInfo struct {
Size uint32
FilePath uintptr
File uintptr
Flags uint32
}
func verifySignature(filePath string, trustedCerts []*x509.Certificate) error {
// 加载Windows API
wintrust := windows.NewLazySystemDLL("wintrust.dll")
winVerifyTrust := wintrust.NewProc("WinVerifyTrust")
// 准备文件信息
fileInfo := WinTrustFileInfo{
Size: uint32(unsafe.Sizeof(WinTrustFileInfo{})),
Flags: 0,
}
utf16Path, err := windows.UTF16PtrFromString(filePath)
if err != nil {
return err
}
fileInfo.FilePath = uintptr(unsafe.Pointer(utf16Path))
// 准备信任数据
trustData := WinTrustData{
Size: uint32(unsafe.Sizeof(WinTrustData{})),
UIChoice: WTD_UI_NONE,
RevocationFlags: WTD_REVOKE_NONE,
UnionChoice: WTD_CHOICE_FILE,
FileOrCatalog: uintptr(unsafe.Pointer(&fileInfo)),
ProviderFlags: 0x00000080, // WTD_CACHE_ONLY_URL_RETRIEVAL
}
// 调用Windows API验证签名
ret, _, _ := winVerifyTrust.Call(
windows.InvalidHandle,
uintptr(unsafe.Pointer(&windows.GUID{Data1: 0x00AAC56B, Data2: 0xCD44, Data3: 0x11D0, Data4: [8]byte{0x8C, 0xC2, 0x00, 0xC0, 0x4F, 0xC2, 0x95, 0xEE}})),
uintptr(unsafe.Pointer(&trustData)),
)
if ret != 0 {
return errors.New("签名验证失败")
}
// 提取并验证证书链
return verifyCertificateChain(filePath, trustedCerts)
}
func verifyCertificateChain(filePath string, trustedCerts []*x509.Certificate) error {
// 打开文件读取签名信息
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close()
// 读取PE头
var dosHeader [64]byte
if _, err := io.ReadFull(file, dosHeader[:]); err != nil {
return err
}
// 获取PE头偏移
peOffset := binary.LittleEndian.Uint32(dosHeader[60:64])
file.Seek(int64(peOffset), io.SeekStart)
// 读取PE签名目录
var peHeader [24]byte
if _, err := io.ReadFull(file, peHeader[:]); err != nil {
return err
}
// 获取数据目录数量
dataDirCount := binary.LittleEndian.Uint32(peHeader[20:24])
// 跳转到数据目录
file.Seek(int64(peOffset)+24+int64(dataDirCount)*8, io.SeekStart)
// 查找证书目录
for i := 0; i < int(dataDirCount); i++ {
var dataDir [8]byte
if _, err := io.ReadFull(file, dataDir[:]); err != nil {
return err
}
// 检查是否为证书目录
if binary.LittleEndian.Uint32(dataDir[0:4]) != 0 &&
binary.LittleEndian.Uint32(dataDir[4:8]) != 0 {
// 提取证书数据
certOffset := binary.LittleEndian.Uint32(dataDir[0:4])
certSize := binary.LittleEndian.Uint32(dataDir[4:8])
file.Seek(int64(certOffset), io.SeekStart)
certData := make([]byte, certSize)
if _, err := io.ReadFull(file, certData); err != nil {
return err
}
// 解析证书
certs, err := parsePKCS7(certData)
if err != nil {
return err
}
// 验证证书链
for _, cert := range certs {
for _, trustedCert := range trustedCerts {
if cert.Equal(trustedCert) {
return nil
}
}
}
}
}
return errors.New("未找到受信任的证书")
}
func parsePKCS7(data []byte) ([]*x509.Certificate, error) {
// PKCS7 SignedData结构
type PKCS7 struct {
ContentType asn1.ObjectIdentifier
SignedData struct {
Version int
DigestAlgorithms []asn1.RawValue
ContentInfo asn1.RawValue
Certificates []asn1.RawValue `asn1:"optional,tag:0"`
} `asn1:"explicit,tag:0"`
}
var pkcs7 PKCS7
_, err := asn1.Unmarshal(data, &pkcs7)
if err != nil {
return nil, err
}
var certs []*x509.Certificate
for _, certData := range pkcs7.SignedData.Certificates {
cert, err := x509.ParseCertificate(certData.FullBytes)
if err != nil {
continue
}
certs = append(certs, cert)
}
return certs, nil
}
func main() {
// 加载受信任的证书
trustedCertPEM := []byte(`-----BEGIN CERTIFICATE-----
... 您的证书数据 ...
-----END CERTIFICATE-----`)
block, _ := pem.Decode(trustedCertPEM)
if block == nil {
panic("无法解析证书")
}
trustedCert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
panic(err)
}
// 验证文件签名
err = verifySignature("test.ps1", []*x509.Certificate{trustedCert})
if err != nil {
fmt.Printf("验证失败: %v\n", err)
} else {
fmt.Println("验证成功")
}
}
对于非PE文件(如.ps1),可以使用Windows的Catalog签名验证:
func verifyCatalogSignature(filePath string) error {
crypt32 := windows.NewLazySystemDLL("crypt32.dll")
cryptCATAdmin := windows.NewLazySystemDLL("cryptnet.dll")
// 初始化Catalog管理员上下文
var hCatAdmin uintptr
cryptCATAdmin.NewProc("CryptCATAdminAcquireContext").Call(
uintptr(unsafe.Pointer(&hCatAdmin)),
uintptr(unsafe.Pointer(&windows.GUID{Data1: 0xDE351A43, Data2: 0x8E59, Data3: 0x11D0, Data4: [8]byte{0x8C, 0x47, 0x00, 0xC0, 0x4F, 0xC2, 0x95, 0xEE}})),
0,
)
// 计算文件哈希
var hash [100]byte
var hashSize uint32 = uint32(len(hash))
cryptCATAdmin.NewProc("CryptCATAdminCalcHashFromFileHandle").Call(
uintptr(unsafe.Pointer(file.Fd())),
uintptr(unsafe.Pointer(&hashSize)),
uintptr(unsafe.Pointer(&hash[0])),
)
// 查找Catalog
var hCatInfo uintptr
cryptCATAdmin.NewProc("CryptCATAdminEnumCatalogFromHash").Call(
hCatAdmin,
uintptr(unsafe.Pointer(&hash[0])),
hashSize,
0,
uintptr(unsafe.Pointer(&hCatInfo)),
)
// 验证Catalog签名
var trustData WinTrustData
trustData.Size = uint32(unsafe.Sizeof(trustData))
trustData.UnionChoice = 2 // WTD_CHOICE_CATALOG
ret, _, _ := wintrust.NewProc("WinVerifyTrust").Call(
windows.InvalidHandle,
uintptr(unsafe.Pointer(&windows.GUID{Data1: 0x00AAC56B, Data2: 0xCD44, Data3: 0x11D0, Data4: [8]byte{0x8C, 0xC2, 0x00, 0xC0, 0x4F, 0xC2, 0x95, 0xEE}})),
uintptr(unsafe.Pointer(&trustData)),
)
return nil
}
这个实现通过Windows API验证文件签名完整性,并使用Go的x509包验证证书链。验证过程确保文件自签名后未被修改,且签名证书在受信任证书列表中。

