//go:build windows

package winpwn

import (
	"fmt"
	"unsafe"

	"golang.org/x/sys/windows"
)

// guid mirrors the Win32 GUID struct layout.
type guid struct {
	Data1 uint32
	Data2 uint16
	Data3 uint16
	Data4 [8]byte
}

// WINTRUST_ACTION_GENERIC_VERIFY_V2, from wintrust.h.
var wintrustActionGenericVerifyV2 = guid{
	Data1: 0x00aac56b,
	Data2: 0xcd44,
	Data3: 0x11d0,
	Data4: [8]byte{0x8c, 0xc2, 0x00, 0xc0, 0x4f, 0xc2, 0x95, 0xee},
}

// wintrustFileInfo mirrors WINTRUST_FILE_INFO (wintrust.h).
type wintrustFileInfo struct {
	CbStruct       uint32
	PcwszFilePath  *uint16
	HFile          windows.Handle
	PgKnownSubject *guid
}

// wintrustData mirrors WINTRUST_DATA (wintrust.h). The struct is normally a
// union of pFile/pCatalog/pBlob/pSgnr/pCert at the PFile position; we only
// ever populate the file-info variant.
type wintrustData struct {
	CbStruct            uint32
	PPolicyCallbackData uintptr
	PSIPClientData      uintptr
	DwUIChoice          uint32
	FdwRevocationChecks uint32
	DwUnionChoice       uint32
	PFile               *wintrustFileInfo
	DwStateAction       uint32
	HWVTStateData       windows.Handle
	PwszURLReference    *uint16
	DwProvFlags         uint32
	DwUIContext         uint32
	PSignatureSettings  uintptr
}

const (
	wtdUINone     = 2
	wtdRevokeNone = 0
	wtdChoiceFile = 1

	wtdStateActionVerify = 1
	wtdStateActionClose  = 2

	wtdSaferFlag             = 0x00000100
	wtdCacheOnlyURLRetrieval = 0x00001000
	wtdDisableMD2MD4         = 0x00002000
)

var (
	modWintrust        = windows.NewLazySystemDLL("wintrust.dll")
	procWinVerifyTrust = modWintrust.NewProc("WinVerifyTrust")
)

// VerifyAuthenticodeSignature asks the OS to validate the Authenticode
// signature on path via WinVerifyTrust — a real cryptographic chain/hash
// check, unlike CheckSecResult.AuthenticodeSigned which only checks whether
// a signature directory is present in the PE at all. Revocation checking is
// disabled, so this makes no network calls; it verifies the embedded
// certificate chain and file hash only.
//
// Most Windows system binaries (System32) are catalog-signed (.cat files)
// rather than embedded-signed and will report TRUST_E_NOSIGNATURE here even
// though Windows itself trusts them — this function only validates an
// Authenticode signature embedded directly in the PE.
func VerifyAuthenticodeSignature(path string) (bool, error) {
	pathPtr, err := windows.UTF16PtrFromString(path)
	if err != nil {
		return false, err
	}

	fileInfo := wintrustFileInfo{
		CbStruct:      uint32(unsafe.Sizeof(wintrustFileInfo{})),
		PcwszFilePath: pathPtr,
	}

	data := wintrustData{
		CbStruct:            uint32(unsafe.Sizeof(wintrustData{})),
		DwUIChoice:          wtdUINone,
		FdwRevocationChecks: wtdRevokeNone,
		DwUnionChoice:       wtdChoiceFile,
		PFile:               &fileInfo,
		DwStateAction:       wtdStateActionVerify,
		DwProvFlags:         wtdSaferFlag | wtdCacheOnlyURLRetrieval | wtdDisableMD2MD4,
	}

	ret, _, _ := procWinVerifyTrust.Call(
		0, // hwnd: NULL, dwUIChoice already suppresses any UI
		uintptr(unsafe.Pointer(&wintrustActionGenericVerifyV2)),
		uintptr(unsafe.Pointer(&data)),
	)
	status := uint32(ret)

	// WinVerifyTrust requires releasing the verification state it allocated,
	// regardless of the outcome above.
	data.DwStateAction = wtdStateActionClose
	procWinVerifyTrust.Call(
		0,
		uintptr(unsafe.Pointer(&wintrustActionGenericVerifyV2)),
		uintptr(unsafe.Pointer(&data)),
	)

	if status != 0 {
		return false, fmt.Errorf("WinVerifyTrust: signature not valid (status 0x%X)", status)
	}
	return true, nil
}
