Last active
September 19, 2025 22:16
-
-
Save heaths/ebbca7d956f0b42bbb33193f0837e272 to your computer and use it in GitHub Desktop.
Check Authenticode signature on Windows with Go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //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) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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