| 1 | //go:build windows |
| 2 | |
| 3 | package main |
| 4 | |
| 5 | import ( |
| 6 | "fmt" |
| 7 | "io" |
| 8 | "os" |
| 9 | "unsafe" |
| 10 | |
| 11 | "golang.org/x/sys/windows" |
| 12 | ) |
| 13 | |
| 14 | var readVerifiedWindowsStagedPayloadFn = readVerifiedWindowsStagedPayload |
| 15 | |
| 16 | // readVerifiedWindowsStagedPayload verifies the Authenticode signature and |
| 17 | // reads the payload through one handle that disallows concurrent writes, |
| 18 | // deletes, and renames. The bytes handed to the publisher therefore cannot |
| 19 | // change between signature verification and publication. |
| 20 | func readVerifiedWindowsStagedPayload(path string) ([]byte, error) { |
| 21 | info, err := os.Lstat(path) |
| 22 | if err != nil { |
| 23 | return nil, err |
| 24 | } |
| 25 | if !info.Mode().IsRegular() { |
| 26 | return nil, fmt.Errorf("staged payload is not a regular file") |
| 27 | } |
| 28 | path16, err := windows.UTF16PtrFromString(path) |
| 29 | if err != nil { |
| 30 | return nil, err |
| 31 | } |
| 32 | handle, err := windows.CreateFile( |
| 33 | path16, |
| 34 | windows.GENERIC_READ, |
| 35 | windows.FILE_SHARE_READ, |
| 36 | nil, |
| 37 | windows.OPEN_EXISTING, |
| 38 | windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_SEQUENTIAL_SCAN, |
| 39 | 0, |
| 40 | ) |
| 41 | if err != nil { |
| 42 | return nil, err |
| 43 | } |
| 44 | file := os.NewFile(uintptr(handle), path) |
| 45 | if file == nil { |
| 46 | _ = windows.CloseHandle(handle) |
| 47 | return nil, fmt.Errorf("open staged payload handle") |
| 48 | } |
| 49 | defer file.Close() |
| 50 | |
| 51 | fileInfo, err := file.Stat() |
| 52 | if err != nil { |
| 53 | return nil, err |
| 54 | } |
| 55 | if !fileInfo.Mode().IsRegular() { |
| 56 | return nil, fmt.Errorf("staged payload is not a regular file") |
| 57 | } |
| 58 | |
| 59 | trustFile := &windows.WinTrustFileInfo{ |
| 60 | Size: uint32(unsafe.Sizeof(windows.WinTrustFileInfo{})), |
| 61 | FilePath: path16, |
| 62 | File: handle, |
| 63 | } |
| 64 | trustData := &windows.WinTrustData{ |
| 65 | Size: uint32(unsafe.Sizeof(windows.WinTrustData{})), |
| 66 | UIChoice: windows.WTD_UI_NONE, |
| 67 | RevocationChecks: windows.WTD_REVOKE_NONE, |
| 68 | UnionChoice: windows.WTD_CHOICE_FILE, |
| 69 | FileOrCatalogOrBlobOrSgnrOrCert: unsafe.Pointer(trustFile), |
| 70 | StateAction: windows.WTD_STATEACTION_VERIFY, |
| 71 | UIContext: windows.WTD_UICONTEXT_EXECUTE, |
| 72 | } |
| 73 | verifyErr := windows.WinVerifyTrustEx( |
| 74 | windows.InvalidHWND, |
| 75 | &windows.WINTRUST_ACTION_GENERIC_VERIFY_V2, |
| 76 | trustData, |
| 77 | ) |
| 78 | trustData.StateAction = windows.WTD_STATEACTION_CLOSE |
| 79 | closeErr := windows.WinVerifyTrustEx( |
| 80 | windows.InvalidHWND, |
| 81 | &windows.WINTRUST_ACTION_GENERIC_VERIFY_V2, |
| 82 | trustData, |
| 83 | ) |
| 84 | if verifyErr != nil { |
| 85 | if closeErr != nil { |
| 86 | return nil, fmt.Errorf("Authenticode verification failed: %v (release trust state: %w)", verifyErr, closeErr) |
| 87 | } |
| 88 | return nil, fmt.Errorf("Authenticode verification failed: %w", verifyErr) |
| 89 | } |
| 90 | if closeErr != nil { |
| 91 | return nil, fmt.Errorf("release Authenticode verification state: %w", closeErr) |
| 92 | } |
| 93 | if _, err := file.Seek(0, io.SeekStart); err != nil { |
| 94 | return nil, err |
| 95 | } |
| 96 | return io.ReadAll(file) |
| 97 | } |
| 98 |