Files
sing-box/experimental/boxdd/authenticode_windows.go
2026-07-13 15:08:24 +08:00

132 lines
4.9 KiB
Go

//go:build windows
package main
import (
"bytes"
"crypto/x509"
"time"
"unsafe"
E "github.com/sagernet/sing/common/exceptions"
"golang.org/x/sys/windows"
)
var (
winTrustLibrary = windows.NewLazySystemDLL("wintrust.dll")
winTrustProviderDataFromStateDataProcedure = winTrustLibrary.NewProc("WTHelperProvDataFromStateData")
winTrustProviderSignerFromChainProcedure = winTrustLibrary.NewProc("WTHelperGetProvSignerFromChain")
winTrustProviderCertificateFromChainProcedure = winTrustLibrary.NewProc("WTHelperGetProvCertFromChain")
)
type cryptProviderCertificate struct {
structureSize uint32
certificateContext *windows.CertContext
}
func authenticodeSigner(path string, file windows.Handle) ([]byte, error) {
pathPointer, err := windows.UTF16PtrFromString(path)
if err != nil {
return nil, err
}
fileInformation := windows.WinTrustFileInfo{
Size: uint32(unsafe.Sizeof(windows.WinTrustFileInfo{})),
FilePath: pathPointer,
File: file,
}
trustData := windows.WinTrustData{
Size: uint32(unsafe.Sizeof(windows.WinTrustData{})),
UIChoice: windows.WTD_UI_NONE,
RevocationChecks: windows.WTD_REVOKE_NONE,
UnionChoice: windows.WTD_CHOICE_FILE,
StateAction: windows.WTD_STATEACTION_VERIFY,
FileOrCatalogOrBlobOrSgnrOrCert: unsafe.Pointer(&fileInformation),
ProvFlags: windows.WTD_CACHE_ONLY_URL_RETRIEVAL |
windows.WTD_REVOCATION_CHECK_NONE |
windows.WTD_DISABLE_MD2_MD4,
UIContext: windows.WTD_UICONTEXT_EXECUTE,
}
trustError := windows.WinVerifyTrustEx(windows.InvalidHWND, &windows.WINTRUST_ACTION_GENERIC_VERIFY_V2, &trustData)
if trustError != nil && !E.IsMulti(
trustError,
windows.Errno(windows.CERT_E_UNTRUSTEDROOT),
windows.Errno(windows.CERT_E_CHAINING),
) {
trustData.StateAction = windows.WTD_STATEACTION_CLOSE
windows.WinVerifyTrustEx(windows.InvalidHWND, &windows.WINTRUST_ACTION_GENERIC_VERIFY_V2, &trustData)
return nil, E.Cause(trustError, "verify Authenticode signature")
}
signer, signerError := verifiedSignerCertificate(trustData.StateData)
trustData.StateAction = windows.WTD_STATEACTION_CLOSE
closeError := windows.WinVerifyTrustEx(windows.InvalidHWND, &windows.WINTRUST_ACTION_GENERIC_VERIFY_V2, &trustData)
if signerError != nil {
return nil, signerError
}
if closeError != nil {
return nil, E.Cause(closeError, "close Authenticode verification")
}
certificate, err := validateCodeSigningCertificate(signer)
if err != nil {
return nil, err
}
if trustError != nil {
err = validateUntrustedSelfSignedCertificate(certificate, time.Now())
if err != nil {
return nil, err
}
}
return signer, nil
}
func verifiedSignerCertificate(stateData windows.Handle) ([]byte, error) {
providerData, _, _ := winTrustProviderDataFromStateDataProcedure.Call(uintptr(stateData))
if providerData == 0 {
return nil, E.New("missing Authenticode provider data")
}
providerSigner, _, _ := winTrustProviderSignerFromChainProcedure.Call(providerData, 0, 0, 0)
if providerSigner == 0 {
return nil, E.New("missing Authenticode provider signer")
}
providerCertificate, _, _ := winTrustProviderCertificateFromChainProcedure.Call(providerSigner, 0)
if providerCertificate == 0 {
return nil, E.New("missing Authenticode provider certificate")
}
certificateContext := (*cryptProviderCertificate)(unsafe.Pointer(providerCertificate)).certificateContext
if certificateContext == nil {
return nil, E.New("empty Authenticode signer certificate context")
}
if certificateContext.Length == 0 || certificateContext.EncodedCert == nil {
return nil, E.New("empty Authenticode signer certificate")
}
encodedCertificate := unsafe.Slice(certificateContext.EncodedCert, int(certificateContext.Length))
return append([]byte(nil), encodedCertificate...), nil
}
func validateCodeSigningCertificate(encodedCertificate []byte) (*x509.Certificate, error) {
certificate, err := x509.ParseCertificate(encodedCertificate)
if err != nil {
return nil, E.Cause(err, "parse Authenticode signer certificate")
}
for _, usage := range certificate.ExtKeyUsage {
if usage == x509.ExtKeyUsageCodeSigning || usage == x509.ExtKeyUsageAny {
return certificate, nil
}
}
return nil, E.New("Authenticode signer certificate is not valid for code signing")
}
func validateUntrustedSelfSignedCertificate(certificate *x509.Certificate, currentTime time.Time) error {
if !bytes.Equal(certificate.RawSubject, certificate.RawIssuer) {
return E.New("untrusted Authenticode signer certificate is not self-signed")
}
err := certificate.CheckSignature(certificate.SignatureAlgorithm, certificate.RawTBSCertificate, certificate.Signature)
if err != nil {
return E.Cause(err, "verify untrusted Authenticode signer self-signature")
}
if currentTime.Before(certificate.NotBefore) || currentTime.After(certificate.NotAfter) {
return E.New("untrusted Authenticode signer certificate is not currently valid")
}
return nil
}