From 7adf15f90e610c59d0c3dea4a2829723505137e5 Mon Sep 17 00:00:00 2001 From: Aaron Klotz Date: Tue, 6 Jun 2023 10:08:10 -0600 Subject: [PATCH] cmd/tailscale/cli, util/winutil/authenticode: flesh out authenticode support Previously, tailscale upgrade was doing the bare minimum for checking authenticode signatures via `WinVerifyTrustEx`. This is fine, but we can do better: * WinVerifyTrustEx verifies that the binary's signature is valid, but it doesn't determine *whose* signature is valid; tailscale upgrade should also ensure that the binary is actually signed *by us*. * I added the ability to check the signatures of MSI files. * In future PRs I will be adding diagnostic logging that lists details about every module (ie, DLL) loaded into our process. As part of that metadata, I want to be able to extract information about who signed the binaries. This code is modelled on some C++ I wrote for Firefox back in the day. See https://searchfox.org/mozilla-central/rev/27e4816536c891d85d63695025f2549fd7976392/toolkit/xre/dllservices/mozglue/Authenticode.cpp for reference. Fixes #8284 Signed-off-by: Aaron Klotz --- cmd/tailscale/cli/authenticode_windows.go | 30 +- cmd/tailscale/depaware.txt | 9 +- go.mod | 2 +- go.sum | 4 +- .../authenticode/authenticode_windows.go | 512 ++++++++++++++++++ util/winutil/authenticode/mksyscall.go | 18 + util/winutil/authenticode/zsyscall_windows.go | 135 +++++ 7 files changed, 681 insertions(+), 29 deletions(-) create mode 100644 util/winutil/authenticode/authenticode_windows.go create mode 100644 util/winutil/authenticode/mksyscall.go create mode 100644 util/winutil/authenticode/zsyscall_windows.go diff --git a/cmd/tailscale/cli/authenticode_windows.go b/cmd/tailscale/cli/authenticode_windows.go index 2662a6e68..12a44e435 100644 --- a/cmd/tailscale/cli/authenticode_windows.go +++ b/cmd/tailscale/cli/authenticode_windows.go @@ -6,33 +6,15 @@ package cli import ( - "unsafe" - - "golang.org/x/sys/windows" + "tailscale.com/util/winutil/authenticode" ) func init() { - verifyAuthenticode = verifyAuthenticodeWindows + verifyAuthenticode = verifyTailscale } -func verifyAuthenticodeWindows(path string) error { - path16, err := windows.UTF16PtrFromString(path) - if err != nil { - return err - } - data := &windows.WinTrustData{ - Size: uint32(unsafe.Sizeof(windows.WinTrustData{})), - UIChoice: windows.WTD_UI_NONE, - RevocationChecks: windows.WTD_REVOKE_WHOLECHAIN, // Full revocation checking, as this is called with network connectivity. - UnionChoice: windows.WTD_CHOICE_FILE, - StateAction: windows.WTD_STATEACTION_VERIFY, - FileOrCatalogOrBlobOrSgnrOrCert: unsafe.Pointer(&windows.WinTrustFileInfo{ - Size: uint32(unsafe.Sizeof(windows.WinTrustFileInfo{})), - FilePath: path16, - }), - } - err = windows.WinVerifyTrustEx(windows.InvalidHWND, &windows.WINTRUST_ACTION_GENERIC_VERIFY_V2, data) - data.StateAction = windows.WTD_STATEACTION_CLOSE - windows.WinVerifyTrustEx(windows.InvalidHWND, &windows.WINTRUST_ACTION_GENERIC_VERIFY_V2, data) - return err +const certSubjectTailscale = "Tailscale Inc." + +func verifyTailscale(path string) error { + return authenticode.Verify(path, certSubjectTailscale) } diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index c7dc13eab..920342fcc 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -11,6 +11,8 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy L github.com/coreos/go-iptables/iptables from tailscale.com/util/linuxfw + W 💣 github.com/dblohm7/wingoes from tailscale.com/util/winutil/authenticode + W 💣 github.com/dblohm7/wingoes/pe from tailscale.com/util/winutil/authenticode github.com/fxamacker/cbor/v2 from tailscale.com/tka github.com/golang/groupcache/lru from tailscale.com/net/dnscache L github.com/google/nftables from tailscale.com/util/linuxfw @@ -66,7 +68,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/atomicfile from tailscale.com/ipn+ tailscale.com/client/tailscale from tailscale.com/cmd/tailscale/cli+ tailscale.com/client/tailscale/apitype from tailscale.com/cmd/tailscale/cli+ - 💣 tailscale.com/cmd/tailscale/cli from tailscale.com/cmd/tailscale + tailscale.com/cmd/tailscale/cli from tailscale.com/cmd/tailscale tailscale.com/control/controlbase from tailscale.com/control/controlhttp tailscale.com/control/controlhttp from tailscale.com/cmd/tailscale/cli tailscale.com/control/controlknobs from tailscale.com/net/portmapper @@ -144,6 +146,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/util/singleflight from tailscale.com/net/dnscache tailscale.com/util/slicesx from tailscale.com/net/dnscache+ 💣 tailscale.com/util/winutil from tailscale.com/hostinfo+ + W 💣 tailscale.com/util/winutil/authenticode from tailscale.com/cmd/tailscale/cli tailscale.com/version from tailscale.com/cmd/tailscale/cli+ tailscale.com/version/distro from tailscale.com/cmd/tailscale/cli+ tailscale.com/wgengine/capture from tailscale.com/cmd/tailscale/cli @@ -194,7 +197,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep bytes from bufio+ compress/flate from compress/gzip+ compress/gzip from net/http - compress/zlib from image/png + compress/zlib from image/png+ container/list from crypto/tls+ context from crypto/tls+ crypto from crypto/ecdsa+ @@ -219,6 +222,8 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep crypto/x509 from crypto/tls+ crypto/x509/pkix from crypto/x509+ database/sql/driver from github.com/google/uuid + W debug/dwarf from debug/pe + W debug/pe from github.com/dblohm7/wingoes/pe embed from tailscale.com/cmd/tailscale/cli+ encoding from encoding/json+ encoding/asn1 from crypto/x509+ diff --git a/go.mod b/go.mod index 0b8593230..c19dcc935 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/creack/pty v1.1.18 github.com/dave/jennifer v1.6.1 - github.com/dblohm7/wingoes v0.0.0-20230426155039-111c8c3b57c8 + github.com/dblohm7/wingoes v0.0.0-20230801195049-ed8077baf0cd github.com/dsnet/try v0.0.3 github.com/evanw/esbuild v0.14.53 github.com/frankban/quicktest v1.14.5 diff --git a/go.sum b/go.sum index 2de14ce05..fe6b39f2b 100644 --- a/go.sum +++ b/go.sum @@ -240,8 +240,8 @@ github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dblohm7/wingoes v0.0.0-20230426155039-111c8c3b57c8 h1:vtIE3GO4hKplR58aTRx3yLPqAbfWyoyYrE8PXUv0Prw= -github.com/dblohm7/wingoes v0.0.0-20230426155039-111c8c3b57c8/go.mod h1:6NCrWM5jRefaG7iN0iMShPalLsljHWBh9v1zxM2f8Xs= +github.com/dblohm7/wingoes v0.0.0-20230801195049-ed8077baf0cd h1:zYVpYS5d3Uf04vVCJuzqpOCwQQIzJibtOx8ivt7zt2Q= +github.com/dblohm7/wingoes v0.0.0-20230801195049-ed8077baf0cd/go.mod h1:6NCrWM5jRefaG7iN0iMShPalLsljHWBh9v1zxM2f8Xs= github.com/denis-tingaikin/go-header v0.4.3 h1:tEaZKAlqql6SKCY++utLmkPLd6K8IBM20Ha7UVm+mtU= github.com/denis-tingaikin/go-header v0.4.3/go.mod h1:0wOCWuN71D5qIgE2nz9KrKmuYBAC2Mra5RassOIQ2/c= github.com/denis-tingajkin/go-header v0.3.1/go.mod h1:sq/2IxMhaZX+RRcgHfCRx/m0M5na0fBt4/CRe7Lrji0= diff --git a/util/winutil/authenticode/authenticode_windows.go b/util/winutil/authenticode/authenticode_windows.go new file mode 100644 index 000000000..4393da478 --- /dev/null +++ b/util/winutil/authenticode/authenticode_windows.go @@ -0,0 +1,512 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +package authenticode + +import ( + "encoding/hex" + "errors" + "fmt" + "path/filepath" + "strings" + "unsafe" + + "github.com/dblohm7/wingoes" + "github.com/dblohm7/wingoes/pe" + "golang.org/x/sys/windows" +) + +var ( + // ErrSigNotFound is returned if no authenticode signature could be found. + ErrSigNotFound = errors.New("authenticode signature not found") + // ErrUnexpectedCertSubject is wrapped with the actual cert subject and + // returned when the binary is signed by a different subject than expected. + ErrUnexpectedCertSubject = errors.New("unexpected cert subject") + errCertSubjectNotFound = errors.New("cert subject not found") + errCertSubjectDecodeLenMismatch = errors.New("length mismatch while decoding cert subject") +) + +const ( + _CERT_STRONG_SIGN_OID_INFO_CHOICE = 2 + _CMSG_SIGNER_CERT_INFO_PARAM = 7 + _MSI_INVALID_HASH_IS_FATAL = 1 + _TRUST_E_NOSIGNATURE = wingoes.HRESULT(-((0x800B0100 ^ 0xFFFFFFFF) + 1)) +) + +// Verify performs authenticode verification on the file at path, and also +// ensures that expectedCertSubject was the entity who signed it. path may point +// to either a PE binary or an MSI package. ErrSigNotFound is returned if no +// signature is found. +func Verify(path string, expectedCertSubject string) error { + path16, err := windows.UTF16PtrFromString(path) + if err != nil { + return err + } + + var subject string + if strings.EqualFold(filepath.Ext(path), ".msi") { + subject, err = verifyMSI(path16) + } else { + subject, _, err = queryPE(path16, true) + } + + if err != nil { + return err + } + + if subject != expectedCertSubject { + return fmt.Errorf("%w %q", ErrUnexpectedCertSubject, subject) + } + + return nil +} + +// SigProvenance indicates whether an authenticode signature was embedded within +// the file itself, or the signature applies to an associated catalog file. +type SigProvenance int + +const ( + SigProvUnknown = SigProvenance(iota) + SigProvEmbedded + SigProvCatalog +) + +// QueryCertSubject obtains the subject associated with the certificate used to +// sign the PE binary located at path. When err == nil, it also returns the +// provenance of that signature. ErrSigNotFound is returned if no signature +// is found. Note that this function does *not* validate the chain of trust; use +// Verify for that purpose! +func QueryCertSubject(path string) (certSubject string, provenance SigProvenance, err error) { + path16, err := windows.UTF16PtrFromString(path) + if err != nil { + return "", SigProvUnknown, err + } + + return queryPE(path16, false) +} + +func queryPE(utf16Path *uint16, verify bool) (string, SigProvenance, error) { + certSubject, err := queryEmbeddedCertSubject(utf16Path, verify) + + switch { + case err == ErrSigNotFound: + // Try looking for the signature in a catalog file. + default: + return certSubject, SigProvEmbedded, err + } + + certSubject, err = queryCatalogCertSubject(utf16Path, verify) + switch { + case err == ErrSigNotFound: + return "", SigProvUnknown, err + default: + return certSubject, SigProvCatalog, err + } +} + +type CertSubjectError struct { + Err error + Subject string +} + +func (e *CertSubjectError) Error() string { + if e == nil { + return "" + } + if e.Subject == "" { + return e.Err.Error() + } + return fmt.Sprintf("cert subject %q: %v", e.Subject, e.Err) +} + +func (e *CertSubjectError) Unwrap() error { + return e.Err +} + +func verifyMSI(path *uint16) (string, error) { + var certCtx *windows.CertContext + hr := msiGetFileSignatureInformation(path, _MSI_INVALID_HASH_IS_FATAL, &certCtx, nil, nil) + if e := wingoes.ErrorFromHRESULT(hr); e.Failed() { + if e == wingoes.ErrorFromHRESULT(_TRUST_E_NOSIGNATURE) { + return "", ErrSigNotFound + } + return "", e + } + defer windows.CertFreeCertificateContext(certCtx) + + return certSubjectFromCertContext(certCtx) +} + +func certSubjectFromCertContext(certCtx *windows.CertContext) (string, error) { + desiredLen := windows.CertGetNameString( + certCtx, + windows.CERT_NAME_SIMPLE_DISPLAY_TYPE, + 0, + nil, + nil, + 0, + ) + if desiredLen <= 1 { + return "", errCertSubjectNotFound + } + + buf := make([]uint16, desiredLen) + actualLen := windows.CertGetNameString( + certCtx, + windows.CERT_NAME_SIMPLE_DISPLAY_TYPE, + 0, + nil, + &buf[0], + desiredLen, + ) + if actualLen != desiredLen { + return "", errCertSubjectDecodeLenMismatch + } + + return windows.UTF16ToString(buf), nil +} + +type objectQuery struct { + certStore windows.Handle + cryptMsg windows.Handle + encodingType uint32 +} + +func newObjectQuery(utf16Path *uint16) (*objectQuery, error) { + var oq objectQuery + if err := windows.CryptQueryObject( + windows.CERT_QUERY_OBJECT_FILE, + unsafe.Pointer(utf16Path), + windows.CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, + windows.CERT_QUERY_FORMAT_FLAG_BINARY, + 0, + &oq.encodingType, + nil, + nil, + &oq.certStore, + &oq.cryptMsg, + nil, + ); err != nil { + return nil, err + } + + return &oq, nil +} + +func (oq *objectQuery) Close() error { + if oq.certStore != 0 { + if err := windows.CertCloseStore(oq.certStore, 0); err != nil { + return err + } + oq.certStore = 0 + } + + if oq.cryptMsg != 0 { + if err := cryptMsgClose(oq.cryptMsg); err != nil { + return err + } + oq.cryptMsg = 0 + } + + return nil +} + +func (oq *objectQuery) certSubject() (string, error) { + var certInfoLen uint32 + if err := cryptMsgGetParam( + oq.cryptMsg, + _CMSG_SIGNER_CERT_INFO_PARAM, + 0, + unsafe.Pointer(nil), + &certInfoLen, + ); err != nil { + return "", err + } + + buf := make([]byte, certInfoLen) + if err := cryptMsgGetParam( + oq.cryptMsg, + _CMSG_SIGNER_CERT_INFO_PARAM, + 0, + unsafe.Pointer(&buf[0]), + &certInfoLen, + ); err != nil { + return "", err + } + + certInfo := (*windows.CertInfo)(unsafe.Pointer(&buf[0])) + certCtx, err := windows.CertFindCertificateInStore( + oq.certStore, + oq.encodingType, + 0, + windows.CERT_FIND_SUBJECT_CERT, + unsafe.Pointer(certInfo), + nil, + ) + if err != nil { + return "", err + } + defer windows.CertFreeCertificateContext(certCtx) + + return certSubjectFromCertContext(certCtx) +} + +func extractCertBlob(hfile windows.Handle) ([]byte, error) { + pef, err := pe.NewPEFromFileHandle(hfile) + if err != nil { + return nil, err + } + defer pef.Close() + + certsAny, err := pef.DataDirectoryEntry(pe.IMAGE_DIRECTORY_ENTRY_SECURITY) + if err != nil { + return nil, err + } + + certs, ok := certsAny.([]pe.AuthenticodeCert) + if !ok || len(certs) == 0 { + return nil, ErrSigNotFound + } + + for _, cert := range certs { + if cert.Revision() != pe.WIN_CERT_REVISION_2_0 || cert.Type() != pe.WIN_CERT_TYPE_PKCS_SIGNED_DATA { + continue + } + return cert.Data(), nil + } + + return nil, ErrSigNotFound +} + +type _HCRYPTPROV windows.Handle + +type _CRYPT_VERIFY_MESSAGE_PARA struct { + CBSize uint32 + MsgAndCertEncodingType uint32 + HCryptProv _HCRYPTPROV + FNGetSignerCertificate uintptr + GetArg uintptr + StrongSignPara *windows.CertStrongSignPara +} + +func querySubjectFromBlob(blob []byte) (string, error) { + para := _CRYPT_VERIFY_MESSAGE_PARA{ + CBSize: uint32(unsafe.Sizeof(_CRYPT_VERIFY_MESSAGE_PARA{})), + MsgAndCertEncodingType: windows.X509_ASN_ENCODING | windows.PKCS_7_ASN_ENCODING, + } + + var certCtx *windows.CertContext + if err := cryptVerifyMessageSignature(¶, 0, &blob[0], uint32(len(blob)), nil, nil, &certCtx); err != nil { + return "", err + } + defer windows.CertFreeCertificateContext(certCtx) + + return certSubjectFromCertContext(certCtx) +} + +func queryEmbeddedCertSubject(utf16Path *uint16, verify bool) (string, error) { + peBinary, err := windows.CreateFile( + utf16Path, + windows.GENERIC_READ, + windows.FILE_SHARE_READ, + nil, + windows.OPEN_EXISTING, + 0, + 0, + ) + if err != nil { + return "", err + } + defer windows.CloseHandle(peBinary) + + blob, err := extractCertBlob(peBinary) + if err != nil { + return "", err + } + + certSubj, err := querySubjectFromBlob(blob) + if err != nil { + return "", err + } + + if !verify { + return certSubj, nil + } + + wintrustArg := unsafe.Pointer(&windows.WinTrustFileInfo{ + Size: uint32(unsafe.Sizeof(windows.WinTrustFileInfo{})), + FilePath: utf16Path, + File: peBinary, + }) + if err := verifyTrust(windows.WTD_CHOICE_FILE, wintrustArg); err != nil { + // We might still want to know who the cert subject claims to be + // even if the validation has failed (eg for troubleshooting purposes), + // so we return a CertSubjectError. + return "", &CertSubjectError{Err: err, Subject: certSubj} + } + + return certSubj, nil +} + +var ( + _BCRYPT_SHA256_ALGORITHM = &([]uint16{'S', 'H', 'A', '2', '5', '6', 0})[0] + _OID_CERT_STRONG_SIGN_OS_1 = &([]byte("1.3.6.1.4.1.311.72.1.1\x00"))[0] +) + +type _HCATADMIN windows.Handle +type _HCATINFO windows.Handle + +type _CATALOG_INFO struct { + size uint32 + catalogFile [windows.MAX_PATH]uint16 +} + +type _WINTRUST_CATALOG_INFO struct { + size uint32 + catalogVersion uint32 + catalogFilePath *uint16 + memberTag *uint16 + memberFilePath *uint16 + memberFile windows.Handle + pCalculatedFileHash *byte + cbCalculatedFileHash uint32 + catalogContext uintptr + catAdmin _HCATADMIN +} + +func queryCatalogCertSubject(utf16Path *uint16, verify bool) (string, error) { + var catAdmin _HCATADMIN + policy := windows.CertStrongSignPara{ + Size: uint32(unsafe.Sizeof(windows.CertStrongSignPara{})), + InfoChoice: _CERT_STRONG_SIGN_OID_INFO_CHOICE, + InfoOrSerializedInfoOrOID: unsafe.Pointer(_OID_CERT_STRONG_SIGN_OS_1), + } + if err := cryptCATAdminAcquireContext2( + &catAdmin, + nil, + _BCRYPT_SHA256_ALGORITHM, + &policy, + 0, + ); err != nil { + return "", err + } + defer cryptCATAdminReleaseContext(catAdmin, 0) + + // We use windows.CreateFile instead of standard library facilities because: + // 1. Subsequent API calls directly utilize the file's Win32 HANDLE; + // 2. We're going to be hashing the contents of this file, so we want to + // provide a sequential-scan hint to the kernel. + memberFile, err := windows.CreateFile( + utf16Path, + windows.GENERIC_READ, + windows.FILE_SHARE_READ, + nil, + windows.OPEN_EXISTING, + windows.FILE_FLAG_SEQUENTIAL_SCAN, + 0, + ) + if err != nil { + return "", err + } + defer windows.CloseHandle(memberFile) + + var hashLen uint32 + if err := cryptCATAdminCalcHashFromFileHandle2( + catAdmin, + memberFile, + &hashLen, + nil, + 0, + ); err != nil { + return "", err + } + + hashBuf := make([]byte, hashLen) + if err := cryptCATAdminCalcHashFromFileHandle2( + catAdmin, + memberFile, + &hashLen, + &hashBuf[0], + 0, + ); err != nil { + return "", err + } + + catInfoCtx, err := cryptCATAdminEnumCatalogFromHash( + catAdmin, + &hashBuf[0], + hashLen, + 0, + nil, + ) + if err != nil { + if err == windows.ERROR_NOT_FOUND { + err = ErrSigNotFound + } + return "", err + } + defer cryptCATAdminReleaseCatalogContext(catAdmin, catInfoCtx, 0) + + catInfo := _CATALOG_INFO{ + size: uint32(unsafe.Sizeof(_CATALOG_INFO{})), + } + if err := cryptCATAdminCatalogInfoFromContext(catInfoCtx, &catInfo, 0); err != nil { + return "", err + } + + oq, err := newObjectQuery(&catInfo.catalogFile[0]) + if err != nil { + return "", err + } + defer oq.Close() + + certSubj, err := oq.certSubject() + if err != nil { + return "", err + } + + if !verify { + return certSubj, nil + } + + // memberTag is required to be formatted this way. + hbh := strings.ToUpper(hex.EncodeToString(hashBuf)) + memberTag, err := windows.UTF16PtrFromString(hbh) + if err != nil { + return "", err + } + + wintrustArg := unsafe.Pointer(&_WINTRUST_CATALOG_INFO{ + size: uint32(unsafe.Sizeof(_WINTRUST_CATALOG_INFO{})), + catalogFilePath: &catInfo.catalogFile[0], + memberTag: memberTag, + memberFilePath: utf16Path, + memberFile: memberFile, + catAdmin: catAdmin, + }) + if err := verifyTrust(windows.WTD_CHOICE_CATALOG, wintrustArg); err != nil { + // We might still want to know who the cert subject claims to be + // even if the validation has failed (eg for troubleshooting purposes), + // so we return a CertSubjectError. + return "", &CertSubjectError{Err: err, Subject: certSubj} + } + + return certSubj, nil +} + +func verifyTrust(infoType uint32, info unsafe.Pointer) error { + data := &windows.WinTrustData{ + Size: uint32(unsafe.Sizeof(windows.WinTrustData{})), + UIChoice: windows.WTD_UI_NONE, + RevocationChecks: windows.WTD_REVOKE_WHOLECHAIN, // Full revocation checking, as this is called with network connectivity. + UnionChoice: infoType, + StateAction: windows.WTD_STATEACTION_VERIFY, + FileOrCatalogOrBlobOrSgnrOrCert: info, + } + err := windows.WinVerifyTrustEx(windows.InvalidHWND, &windows.WINTRUST_ACTION_GENERIC_VERIFY_V2, data) + + data.StateAction = windows.WTD_STATEACTION_CLOSE + windows.WinVerifyTrustEx(windows.InvalidHWND, &windows.WINTRUST_ACTION_GENERIC_VERIFY_V2, data) + + return err +} diff --git a/util/winutil/authenticode/mksyscall.go b/util/winutil/authenticode/mksyscall.go new file mode 100644 index 000000000..8b7cabe6e --- /dev/null +++ b/util/winutil/authenticode/mksyscall.go @@ -0,0 +1,18 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +package authenticode + +//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go mksyscall.go +//go:generate go run golang.org/x/tools/cmd/goimports -w zsyscall_windows.go + +//sys cryptCATAdminAcquireContext2(hCatAdmin *_HCATADMIN, pgSubsystem *windows.GUID, hashAlgorithm *uint16, strongHashPolicy *windows.CertStrongSignPara, flags uint32) (err error) [int32(failretval)==0] = wintrust.CryptCATAdminAcquireContext2 +//sys cryptCATAdminCalcHashFromFileHandle2(hCatAdmin _HCATADMIN, file windows.Handle, pcbHash *uint32, pbHash *byte, flags uint32) (err error) [int32(failretval)==0] = wintrust.CryptCATAdminCalcHashFromFileHandle2 +//sys cryptCATAdminCatalogInfoFromContext(hCatInfo _HCATINFO, catInfo *_CATALOG_INFO, flags uint32) (err error) [int32(failretval)==0] = wintrust.CryptCATCatalogInfoFromContext +//sys cryptCATAdminEnumCatalogFromHash(hCatAdmin _HCATADMIN, pbHash *byte, cbHash uint32, flags uint32, prevCatInfo *_HCATINFO) (ret _HCATINFO, err error) [ret==0] = wintrust.CryptCATAdminEnumCatalogFromHash +//sys cryptCATAdminReleaseCatalogContext(hCatAdmin _HCATADMIN, hCatInfo _HCATINFO, flags uint32) (err error) [int32(failretval)==0] = wintrust.CryptCATAdminReleaseCatalogContext +//sys cryptCATAdminReleaseContext(hCatAdmin _HCATADMIN, flags uint32) (err error) [int32(failretval)==0] = wintrust.CryptCATAdminReleaseContext +//sys cryptMsgClose(cryptMsg windows.Handle) (err error) [int32(failretval)==0] = crypt32.CryptMsgClose +//sys cryptMsgGetParam(cryptMsg windows.Handle, paramType uint32, index uint32, data unsafe.Pointer, dataLen *uint32) (err error) [int32(failretval)==0] = crypt32.CryptMsgGetParam +//sys cryptVerifyMessageSignature(pVerifyPara *_CRYPT_VERIFY_MESSAGE_PARA, signerIndex uint32, pbSignedBlob *byte, cbSignedBlob uint32, pbDecoded *byte, pdbDecoded *uint32, ppSignerCert **windows.CertContext) (err error) [int32(failretval)==0] = crypt32.CryptVerifyMessageSignature +//sys msiGetFileSignatureInformation(signedObjectPath *uint16, flags uint32, certCtx **windows.CertContext, pbHashData *byte, cbHashData *uint32) (ret wingoes.HRESULT) = msi.MsiGetFileSignatureInformationW diff --git a/util/winutil/authenticode/zsyscall_windows.go b/util/winutil/authenticode/zsyscall_windows.go new file mode 100644 index 000000000..643721e06 --- /dev/null +++ b/util/winutil/authenticode/zsyscall_windows.go @@ -0,0 +1,135 @@ +// Code generated by 'go generate'; DO NOT EDIT. + +package authenticode + +import ( + "syscall" + "unsafe" + + "github.com/dblohm7/wingoes" + "golang.org/x/sys/windows" +) + +var _ unsafe.Pointer + +// Do the interface allocations only once for common +// Errno values. +const ( + errnoERROR_IO_PENDING = 997 +) + +var ( + errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) + errERROR_EINVAL error = syscall.EINVAL +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +func errnoErr(e syscall.Errno) error { + switch e { + case 0: + return errERROR_EINVAL + case errnoERROR_IO_PENDING: + return errERROR_IO_PENDING + } + // TODO: add more here, after collecting data on the common + // error values see on Windows. (perhaps when running + // all.bat?) + return e +} + +var ( + modcrypt32 = windows.NewLazySystemDLL("crypt32.dll") + modmsi = windows.NewLazySystemDLL("msi.dll") + modwintrust = windows.NewLazySystemDLL("wintrust.dll") + + procCryptMsgClose = modcrypt32.NewProc("CryptMsgClose") + procCryptMsgGetParam = modcrypt32.NewProc("CryptMsgGetParam") + procCryptVerifyMessageSignature = modcrypt32.NewProc("CryptVerifyMessageSignature") + procMsiGetFileSignatureInformationW = modmsi.NewProc("MsiGetFileSignatureInformationW") + procCryptCATAdminAcquireContext2 = modwintrust.NewProc("CryptCATAdminAcquireContext2") + procCryptCATAdminCalcHashFromFileHandle2 = modwintrust.NewProc("CryptCATAdminCalcHashFromFileHandle2") + procCryptCATAdminEnumCatalogFromHash = modwintrust.NewProc("CryptCATAdminEnumCatalogFromHash") + procCryptCATAdminReleaseCatalogContext = modwintrust.NewProc("CryptCATAdminReleaseCatalogContext") + procCryptCATAdminReleaseContext = modwintrust.NewProc("CryptCATAdminReleaseContext") + procCryptCATCatalogInfoFromContext = modwintrust.NewProc("CryptCATCatalogInfoFromContext") +) + +func cryptMsgClose(cryptMsg windows.Handle) (err error) { + r1, _, e1 := syscall.Syscall(procCryptMsgClose.Addr(), 1, uintptr(cryptMsg), 0, 0) + if int32(r1) == 0 { + err = errnoErr(e1) + } + return +} + +func cryptMsgGetParam(cryptMsg windows.Handle, paramType uint32, index uint32, data unsafe.Pointer, dataLen *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procCryptMsgGetParam.Addr(), 5, uintptr(cryptMsg), uintptr(paramType), uintptr(index), uintptr(data), uintptr(unsafe.Pointer(dataLen)), 0) + if int32(r1) == 0 { + err = errnoErr(e1) + } + return +} + +func cryptVerifyMessageSignature(pVerifyPara *_CRYPT_VERIFY_MESSAGE_PARA, signerIndex uint32, pbSignedBlob *byte, cbSignedBlob uint32, pbDecoded *byte, pdbDecoded *uint32, ppSignerCert **windows.CertContext) (err error) { + r1, _, e1 := syscall.Syscall9(procCryptVerifyMessageSignature.Addr(), 7, uintptr(unsafe.Pointer(pVerifyPara)), uintptr(signerIndex), uintptr(unsafe.Pointer(pbSignedBlob)), uintptr(cbSignedBlob), uintptr(unsafe.Pointer(pbDecoded)), uintptr(unsafe.Pointer(pdbDecoded)), uintptr(unsafe.Pointer(ppSignerCert)), 0, 0) + if int32(r1) == 0 { + err = errnoErr(e1) + } + return +} + +func msiGetFileSignatureInformation(signedObjectPath *uint16, flags uint32, certCtx **windows.CertContext, pbHashData *byte, cbHashData *uint32) (ret wingoes.HRESULT) { + r0, _, _ := syscall.Syscall6(procMsiGetFileSignatureInformationW.Addr(), 5, uintptr(unsafe.Pointer(signedObjectPath)), uintptr(flags), uintptr(unsafe.Pointer(certCtx)), uintptr(unsafe.Pointer(pbHashData)), uintptr(unsafe.Pointer(cbHashData)), 0) + ret = wingoes.HRESULT(r0) + return +} + +func cryptCATAdminAcquireContext2(hCatAdmin *_HCATADMIN, pgSubsystem *windows.GUID, hashAlgorithm *uint16, strongHashPolicy *windows.CertStrongSignPara, flags uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procCryptCATAdminAcquireContext2.Addr(), 5, uintptr(unsafe.Pointer(hCatAdmin)), uintptr(unsafe.Pointer(pgSubsystem)), uintptr(unsafe.Pointer(hashAlgorithm)), uintptr(unsafe.Pointer(strongHashPolicy)), uintptr(flags), 0) + if int32(r1) == 0 { + err = errnoErr(e1) + } + return +} + +func cryptCATAdminCalcHashFromFileHandle2(hCatAdmin _HCATADMIN, file windows.Handle, pcbHash *uint32, pbHash *byte, flags uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procCryptCATAdminCalcHashFromFileHandle2.Addr(), 5, uintptr(hCatAdmin), uintptr(file), uintptr(unsafe.Pointer(pcbHash)), uintptr(unsafe.Pointer(pbHash)), uintptr(flags), 0) + if int32(r1) == 0 { + err = errnoErr(e1) + } + return +} + +func cryptCATAdminEnumCatalogFromHash(hCatAdmin _HCATADMIN, pbHash *byte, cbHash uint32, flags uint32, prevCatInfo *_HCATINFO) (ret _HCATINFO, err error) { + r0, _, e1 := syscall.Syscall6(procCryptCATAdminEnumCatalogFromHash.Addr(), 5, uintptr(hCatAdmin), uintptr(unsafe.Pointer(pbHash)), uintptr(cbHash), uintptr(flags), uintptr(unsafe.Pointer(prevCatInfo)), 0) + ret = _HCATINFO(r0) + if ret == 0 { + err = errnoErr(e1) + } + return +} + +func cryptCATAdminReleaseCatalogContext(hCatAdmin _HCATADMIN, hCatInfo _HCATINFO, flags uint32) (err error) { + r1, _, e1 := syscall.Syscall(procCryptCATAdminReleaseCatalogContext.Addr(), 3, uintptr(hCatAdmin), uintptr(hCatInfo), uintptr(flags)) + if int32(r1) == 0 { + err = errnoErr(e1) + } + return +} + +func cryptCATAdminReleaseContext(hCatAdmin _HCATADMIN, flags uint32) (err error) { + r1, _, e1 := syscall.Syscall(procCryptCATAdminReleaseContext.Addr(), 2, uintptr(hCatAdmin), uintptr(flags), 0) + if int32(r1) == 0 { + err = errnoErr(e1) + } + return +} + +func cryptCATAdminCatalogInfoFromContext(hCatInfo _HCATINFO, catInfo *_CATALOG_INFO, flags uint32) (err error) { + r1, _, e1 := syscall.Syscall(procCryptCATCatalogInfoFromContext.Addr(), 3, uintptr(hCatInfo), uintptr(unsafe.Pointer(catInfo)), uintptr(flags)) + if int32(r1) == 0 { + err = errnoErr(e1) + } + return +}