Skip to content

Instantly share code, notes, and snippets.

@heaths
Last active September 19, 2025 22:16
Show Gist options
  • Select an option

  • Save heaths/ebbca7d956f0b42bbb33193f0837e272 to your computer and use it in GitHub Desktop.

Select an option

Save heaths/ebbca7d956f0b42bbb33193f0837e272 to your computer and use it in GitHub Desktop.
Check Authenticode signature on Windows with Go
//go:build windows
package main
import (
"flag"
"fmt"
"log"
"os"
"path/filepath"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
type WTD_UI uint32
type WTD_REVOKE uint32
type WTD_CHOICE uint32
type WTD_STATEACTION uint32
type WTD_FLAGS uint32
type WTD_UICONTEXT uint32
const (
INVALID_HANDLE_VALUE syscall.Handle = syscall.Handle(1<<(unsafe.Sizeof(uintptr(0))*8-1)<<1 - 1)
WTD_UI_NONE WTD_UI = 2
WTD_REVOKE_NONE WTD_REVOKE = 0
WTD_REVOKE_WHOLECHAIN WTD_REVOKE = 1
WTD_CHOICE_FILE WTD_CHOICE = 1
WTD_STATEACTION_VERIFY WTD_STATEACTION = 1
WTD_STATEACTION_CLOSE WTD_STATEACTION = 2
WTD_REVOCATION_CHECK_NONE WTD_FLAGS = 16
WTD_REVOCATION_CHECK_CHAIN WTD_FLAGS = 64
WTD_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT WTD_FLAGS = 128
WTD_CACHE_ONLY_URL_RETRIEVAL WTD_FLAGS = 4096
WTD_UICONTEXT_EXECUTE WTD_UICONTEXT = 0
WTD_UICONTEXT_INSTALL WTD_UICONTEXT = 1
)
var (
WINTRUST_ACTION_GENERIC_VERIFY_V2 = syscall.GUID{
Data1: 0xaac56b,
Data2: 0xcd44,
Data3: 0x11d0,
Data4: [8]byte{0x8c, 0xc2, 0x0, 0xc0, 0x4f, 0xc2, 0x95, 0xee},
}
modwintrust = windows.NewLazySystemDLL("wintrust.dll")
procWinVerifyTrust = modwintrust.NewProc("WinVerifyTrust")
)
type WinTrustData struct {
cbStruct uint32
pPolicyCallbackData uintptr
pSIPClientData uintptr
dwUIChoice WTD_UI
fdwRevocationChecks WTD_REVOKE
dwUnionChoice WTD_CHOICE
pFile *WINTRUST_FILE_INFO
dwStateAction WTD_STATEACTION
hWVTStateData syscall.Handle
pwszURLReference *uint16
dwProvFlags WTD_FLAGS
dwUIContext WTD_UICONTEXT
}
type WINTRUST_FILE_INFO struct {
cbStruct uint32
pcwszFilePath *uint16
hFile syscall.Handle
pgKnownSubject *syscall.GUID
}
func WinVerifyTrust(
hwnd syscall.Handle,
pgActionID *syscall.GUID,
pWVTData *WinTrustData,
) error {
r1, _, err := syscall.SyscallN(
procWinVerifyTrust.Addr(),
uintptr(hwnd),
uintptr(unsafe.Pointer(pgActionID)),
uintptr(unsafe.Pointer(pWVTData)),
)
if r1 != uintptr(windows.ERROR_SUCCESS) {
return err
}
return nil
}
func main() {
checkRevocation := flag.Bool("check-revocation", false, "Check revocation from the local cache")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Verify the Authenticode signature of a file\n\n")
fmt.Fprintf(os.Stderr, "Usage of %s:\n\b", os.Args[0])
fmt.Fprintf(os.Stderr, " %s [flags] path\n\n", filepath.Base(os.Args[0]))
fmt.Fprintf(os.Stderr, "Arguments\n")
fmt.Fprintf(os.Stderr, " path\n")
fmt.Fprintf(os.Stderr, " Path of the file to verify\n")
fmt.Fprintln(os.Stderr)
fmt.Fprintf(os.Stderr, "Flags:\n")
flag.PrintDefaults()
}
flag.Parse()
path := flag.Arg(0)
if path == "" {
log.Fatal("requires path to verify")
}
wtd, err := NewWinTrustFileData(path, *checkRevocation)
if err != nil {
log.Fatal(err)
}
defer wtd.Close()
err = WinVerifyTrust(INVALID_HANDLE_VALUE, &WINTRUST_ACTION_GENERIC_VERIFY_V2, wtd)
if err != nil {
log.Fatalf("failed to verify %s: %s", path, err)
}
fmt.Printf("%q is valid\n", path)
}
func NewWinTrustFileData(path string, checkRevocation bool) (*WinTrustData, error) {
var err error
fileInfo := new(WINTRUST_FILE_INFO)
fileInfo.cbStruct = uint32(unsafe.Sizeof(*fileInfo))
fileInfo.pcwszFilePath, err = syscall.UTF16PtrFromString(path)
if err != nil {
return nil, err
}
wtd := new(WinTrustData)
wtd.cbStruct = uint32(unsafe.Sizeof(*wtd))
wtd.dwUIChoice = WTD_UI_NONE
wtd.fdwRevocationChecks = WTD_REVOKE_NONE
wtd.dwUnionChoice = WTD_CHOICE_FILE
wtd.dwStateAction = WTD_STATEACTION_VERIFY
wtd.pFile = fileInfo
if checkRevocation {
wtd.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL
} else {
wtd.dwProvFlags = WTD_REVOCATION_CHECK_NONE
}
return wtd, nil
}
func (wtd *WinTrustData) Close() {
wtd.dwStateAction = WTD_STATEACTION_CLOSE
WinVerifyTrust(INVALID_HANDLE_VALUE, &WINTRUST_ACTION_GENERIC_VERIFY_V2, wtd)
}
@Barrixar
Copy link

Barrixar commented Sep 19, 2025

Hello @heaths,

Here: https://gist.github.com/Barrixar/88813b0070dc3ba1896d20aebab089e3 is an extended (feature-rich and security hardened) version of "WinVerify.go", i used your implementation as a template and guideline to work off, because Microsoft folks tend to be knownledgeable on API and such.

My version is 4200 lines, yours is just 160. At the end of my work, no original lines are left, but i can imagine that if you or whoever you know is using your version, is still doing so, you can make good use of my version.

I will also turn it into a repository. It's already very mature as-is, and i invested 100 times as much time in API & internals research than i did coding, to make sure of the maximum extent of resilience and robustness. As you can see it took me over 2 weeks since initially taking yours. I spent some time every day.

My version stood up against pentesting, stress testing, fuzzing, peer reviews, a ton of static analyzers, and leveraged recent publications about the weaknesses associated with file digital signatures and parsing them, including that it isn't prone to the recently published CVE-2013-3900 while checking a file (As you can't expect all systems to have migitated it using registry).

The engineering of my version is of an extremely high grade. That's why i will also seek to do an 1:1 port to C++ in the near future, it'll appear some time after the repository for this Golang version will appear.

EDIT: Repo-ified it by now as well, https://github.com/Barrixar/WinVerifyTrust-Go

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment