From 4eaccce0894b32d28c44599f914a62891cb29bd1 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Tue, 30 Apr 2019 11:41:45 +0200 Subject: version: add certificate checking for official versions This is an easy circumventable check designed mostly for convenience. Signed-off-by: Jason A. Donenfeld --- version/official_windows.go | 51 +++++++++++++++++++++++------ version/wintrust/certificate_windows.go | 58 +++++++++++++++++++++++++++++++++ version/wintrust/mksyscall.go | 2 +- version/wintrust/zsyscall_windows.go | 16 ++++++++- 4 files changed, 115 insertions(+), 12 deletions(-) create mode 100644 version/wintrust/certificate_windows.go (limited to 'version') diff --git a/version/official_windows.go b/version/official_windows.go index 745c2ba6..4a683888 100644 --- a/version/official_windows.go +++ b/version/official_windows.go @@ -12,7 +12,37 @@ import ( "unsafe" ) -func IsOfficialPath(path string) bool { +const ( + officialCommonName = "WireGuard LLC" +) + +func VerifyAuthenticode(path string) bool { + path16, err := windows.UTF16PtrFromString(path) + if err != nil { + return false + } + file := &wintrust.WinTrustFileInfo{ + CbStruct: uint32(unsafe.Sizeof(wintrust.WinTrustFileInfo{})), + FilePath: path16, + } + data := &wintrust.WinTrustData{ + CbStruct: uint32(unsafe.Sizeof(wintrust.WinTrustData{})), + UIChoice: wintrust.WTD_UI_NONE, + RevocationChecks: wintrust.WTD_REVOKE_WHOLECHAIN, // Full revocation checking, as this is called with network connectivity. + UnionChoice: wintrust.WTD_CHOICE_FILE, + StateAction: wintrust.WTD_STATEACTION_VERIFY, + FileOrCatalogOrBlobOrSgnrOrCert: uintptr(unsafe.Pointer(file)), + } + return wintrust.WinVerifyTrust(0, &wintrust.WINTRUST_ACTION_GENERIC_VERIFY_V2, data) == nil +} + +// This is an easily by-passable check, which doesn't serve a security purpose but mostly just a low-grade +// informational and semantic one. +func IsRunningOfficialVersion() bool { + path, err := os.Executable() + if err != nil { + return false + } path16, err := windows.UTF16PtrFromString(path) if err != nil { return false @@ -24,7 +54,7 @@ func IsOfficialPath(path string) bool { data := &wintrust.WinTrustData{ CbStruct: uint32(unsafe.Sizeof(wintrust.WinTrustData{})), UIChoice: wintrust.WTD_UI_NONE, - RevocationChecks: wintrust.WTD_REVOKE_NONE, + RevocationChecks: wintrust.WTD_REVOKE_NONE, // No revocation, as this isn't security related. UnionChoice: wintrust.WTD_CHOICE_FILE, StateAction: wintrust.WTD_STATEACTION_VERIFY, FileOrCatalogOrBlobOrSgnrOrCert: uintptr(unsafe.Pointer(file)), @@ -34,15 +64,16 @@ func IsOfficialPath(path string) bool { return false } - //TODO: check that the certificate actually belongs to us - - return true -} - -func IsOfficial() bool { - path, err := os.Executable() + // This below tests is easily circumvented. False certificates can be appended, and just checking the + // common name is not very good. But that's okay, as this isn't security related. + certs, err := wintrust.ExtractCertificates(path) if err != nil { return false } - return IsOfficialPath(path) + for _, cert := range certs { + if cert.Subject.CommonName == officialCommonName { + return true + } + } + return false } diff --git a/version/wintrust/certificate_windows.go b/version/wintrust/certificate_windows.go new file mode 100644 index 00000000..8a0b5f2f --- /dev/null +++ b/version/wintrust/certificate_windows.go @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +package wintrust + +import ( + "crypto/x509" + "golang.org/x/sys/windows" + "syscall" + "unsafe" +) + +const ( + CERT_QUERY_OBJECT_FILE = 1 + CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED = 1024 + CERT_QUERY_FORMAT_FLAG_ALL = 14 +) + +//sys CryptQueryObject(objectType uint32, object uintptr, expectedContentTypeFlags uint32, expectedFormatTypeFlags uint32, flags uint32, msgAndCertEncodingType *uint32, contentType *uint32, formatType *uint32, certStore *windows.Handle, msg *windows.Handle, context *uintptr) (err error) = crypt32.CryptQueryObject + +func ExtractCertificates(path string) ([]x509.Certificate, error) { + path16, err := windows.UTF16PtrFromString(path) + if err != nil { + return nil, err + } + var certStore windows.Handle + err = CryptQueryObject(CERT_QUERY_OBJECT_FILE, uintptr(unsafe.Pointer(path16)), CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, CERT_QUERY_FORMAT_FLAG_ALL, 0, nil, nil, nil, &certStore, nil, nil) + if err != nil { + return nil, err + } + defer windows.CertCloseStore(certStore, 0) + var certs []x509.Certificate + var cert *windows.CertContext + for { + cert, err = windows.CertEnumCertificatesInStore(certStore, cert) + if err != nil { + if errno, ok := err.(syscall.Errno); ok { + if errno == syscall.Errno(windows.CRYPT_E_NOT_FOUND) { + break + } + } + return nil, err + } + if cert == nil { + break + } + buf := make([]byte, cert.Length) + copy(buf, (*[1 << 20]byte)(unsafe.Pointer(cert.EncodedCert))[:]) + if c, err := x509.ParseCertificate(buf); err == nil { + certs = append(certs, *c) + } else { + return nil, err + } + } + return certs, nil +} diff --git a/version/wintrust/mksyscall.go b/version/wintrust/mksyscall.go index 0a5df80d..5f78d375 100644 --- a/version/wintrust/mksyscall.go +++ b/version/wintrust/mksyscall.go @@ -5,4 +5,4 @@ package wintrust -//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go wintrust_windows.go +//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go wintrust_windows.go certificate_windows.go diff --git a/version/wintrust/zsyscall_windows.go b/version/wintrust/zsyscall_windows.go index 775f38ba..8aa315c0 100644 --- a/version/wintrust/zsyscall_windows.go +++ b/version/wintrust/zsyscall_windows.go @@ -38,8 +38,10 @@ func errnoErr(e syscall.Errno) error { var ( modwintrust = windows.NewLazySystemDLL("wintrust.dll") + modcrypt32 = windows.NewLazySystemDLL("crypt32.dll") - procWinVerifyTrust = modwintrust.NewProc("WinVerifyTrust") + procWinVerifyTrust = modwintrust.NewProc("WinVerifyTrust") + procCryptQueryObject = modcrypt32.NewProc("CryptQueryObject") ) func WinVerifyTrust(hWnd windows.Handle, actionId *windows.GUID, data *WinTrustData) (err error) { @@ -53,3 +55,15 @@ func WinVerifyTrust(hWnd windows.Handle, actionId *windows.GUID, data *WinTrustD } return } + +func CryptQueryObject(objectType uint32, object uintptr, expectedContentTypeFlags uint32, expectedFormatTypeFlags uint32, flags uint32, msgAndCertEncodingType *uint32, contentType *uint32, formatType *uint32, certStore *windows.Handle, msg *windows.Handle, context *uintptr) (err error) { + r1, _, e1 := syscall.Syscall12(procCryptQueryObject.Addr(), 11, uintptr(objectType), uintptr(object), uintptr(expectedContentTypeFlags), uintptr(expectedFormatTypeFlags), uintptr(flags), uintptr(unsafe.Pointer(msgAndCertEncodingType)), uintptr(unsafe.Pointer(contentType)), uintptr(unsafe.Pointer(formatType)), uintptr(unsafe.Pointer(certStore)), uintptr(unsafe.Pointer(msg)), uintptr(unsafe.Pointer(context)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} -- cgit v1.2.3-59-g8ed1b