From a8cc9c4da5d42904037ebe40476c1816905b4556 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Tue, 30 Apr 2019 09:41:36 +0200 Subject: version: add beginnings of authenticode checking --- attacksurface.md | 11 ++++ ui/ui.go | 14 +++++ updater/downloader.go | 10 ++- version/official_linux.go | 12 ++++ version/official_windows.go | 48 +++++++++++++++ version/wintrust/mksyscall.go | 8 +++ version/wintrust/wintrust_windows.go | 115 +++++++++++++++++++++++++++++++++++ 7 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 version/official_linux.go create mode 100644 version/official_windows.go create mode 100644 version/wintrust/mksyscall.go create mode 100644 version/wintrust/wintrust_windows.go diff --git a/attacksurface.md b/attacksurface.md index 5b545393..7e881d59 100644 --- a/attacksurface.md +++ b/attacksurface.md @@ -38,3 +38,14 @@ The UI is an unprivileged process running as the ordinary user for each user who - There currently are no anti-debugging mechanisms, which means any process that can, debug, introspect, inject, send messages, or interact with the UI, may be able to take control of the above handles passed to it by the manager service. In theory that shouldn't be too horrible for the security model, but rather than risk it, we might consider importing all of the insane things VirtualBox does in this domain. Alternatively, since the anti-debugging stuff is pretty ugly and probably doesn't even work properly everywhere, we may be better off with starting the process at some heightened security level, such that only other processes at that level can introspect; however, we'll need to take special care that doing so doesn't give the UI process any special accesses it wouldn't otherwise have. - It renders highlighted config files to a msftedit.dll control, which typically is capable of all sorts of OLE and RTF nastiness that we make some attempt to avoid. +### Updates + +A server hosts the result of: + +``` +$ b2sum -l 256 *.msi > list +$ signify -S -e -s release.sec -m list +$ upload ./list.sec +``` + +The MSIs in that list are only the latest ones available, and filenames fit the form `wireguard-${arch}-${version}.msi`. The updater downloads this list over TLS and verifies the signify Ed25519 signature of it. If it validates, then it finds the first MSI in it for its architecture that has a greater version. It then downloads this MSI from a predefined URL, and verifies the BLAKE2b-256 signature. If it validates, then it calls `WinTrustVerify(WINTRUST_ACTION_GENERIC_VERIFY_V2)` on the MSI. If it validates then it calls `TODO: validate that it's signed with our cert`. If it validates, then it executes the installer with `msiexec.exe`. diff --git a/ui/ui.go b/ui/ui.go index 56d67e65..7cb2744a 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -59,7 +59,21 @@ func RunUI() { } } + //XXX: REMOVE ME! + const keepUpdaterInUnofficialBuild = true + go func() { + if !version.IsOfficial() { + mtw.Synchronize(func() { + mtw.SetTitle(mtw.Title() + " (unofficial/untrusted/unverified build)") + tray.ShowWarning("Unverified WireGuard Build", "The build of WireGuard that you are running is unofficial/untrusted/unverified. You may want to double-check your download source.") + }) + if !keepUpdaterInUnofficialBuild { + // Don't check for updates on unofficial builds. + return + } + } + first := true for { update, err := updater.CheckForUpdate() diff --git a/updater/downloader.go b/updater/downloader.go index ea3ee9d4..c1ca4beb 100644 --- a/updater/downloader.go +++ b/updater/downloader.go @@ -160,13 +160,21 @@ func DownloadVerifyAndExecute() (progress chan DownloadProgress) { } out.Close() out = nil + + progress <- DownloadProgress{Activity: "Verifying authenticode signature"} + if !version.IsOfficialPath(unverifiedDestinationFilename) { + os.Remove(unverifiedDestinationFilename) + progress <- DownloadProgress{Error: errors.New("The downloaded update does not have an authentic authenticode signature")} + return + } + + progress <- DownloadProgress{Activity: "Installing update"} err = os.Rename(unverifiedDestinationFilename, destinationFilename) if err != nil { os.Remove(unverifiedDestinationFilename) progress <- DownloadProgress{Error: err} return } - progress <- DownloadProgress{Activity: "Installing update"} err = runMsi(destinationFilename) os.Remove(unverifiedDestinationFilename) if err != nil { diff --git a/version/official_linux.go b/version/official_linux.go new file mode 100644 index 00000000..d3ca3349 --- /dev/null +++ b/version/official_linux.go @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +package version + +// For testing the updater package from linux. Debug stuff only. + +func IsOfficialPath(path string) bool { + return true +} diff --git a/version/official_windows.go b/version/official_windows.go new file mode 100644 index 00000000..745c2ba6 --- /dev/null +++ b/version/official_windows.go @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +package version + +import ( + "golang.org/x/sys/windows" + "golang.zx2c4.com/wireguard/windows/version/wintrust" + "os" + "unsafe" +) + +func IsOfficialPath(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_NONE, + UnionChoice: wintrust.WTD_CHOICE_FILE, + StateAction: wintrust.WTD_STATEACTION_VERIFY, + FileOrCatalogOrBlobOrSgnrOrCert: uintptr(unsafe.Pointer(file)), + } + err = wintrust.WinVerifyTrust(0, &wintrust.WINTRUST_ACTION_GENERIC_VERIFY_V2, data) + if err != nil { + return false + } + + //TODO: check that the certificate actually belongs to us + + return true +} + +func IsOfficial() bool { + path, err := os.Executable() + if err != nil { + return false + } + return IsOfficialPath(path) +} diff --git a/version/wintrust/mksyscall.go b/version/wintrust/mksyscall.go new file mode 100644 index 00000000..0a5df80d --- /dev/null +++ b/version/wintrust/mksyscall.go @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +package wintrust + +//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go wintrust_windows.go diff --git a/version/wintrust/wintrust_windows.go b/version/wintrust/wintrust_windows.go new file mode 100644 index 00000000..b50636a3 --- /dev/null +++ b/version/wintrust/wintrust_windows.go @@ -0,0 +1,115 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +package wintrust + +import ( + "golang.org/x/sys/windows" + "syscall" +) + +type WinTrustData struct { + CbStruct uint32 + PolicyCallbackData uintptr + SIPClientData uintptr + UIChoice uint32 + RevocationChecks uint32 + UnionChoice uint32 + FileOrCatalogOrBlobOrSgnrOrCert uintptr + StateAction uint32 + StateData syscall.Handle + URLReference *uint16 + ProvFlags uint32 + UIContext uint32 + SignatureSettings *WintrustSignatureSettings +} + +const ( + WTD_UI_ALL = 1 + WTD_UI_NONE = 2 + WTD_UI_NOBAD = 3 + WTD_UI_NOGOOD = 4 +) + +const ( + WTD_REVOKE_NONE = 0 + WTD_REVOKE_WHOLECHAIN = 1 +) + +const ( + WTD_CHOICE_FILE = 1 + WTD_CHOICE_CATALOG = 2 + WTD_CHOICE_BLOB = 3 + WTD_CHOICE_SIGNER = 4 + WTD_CHOICE_CERT = 5 +) + +const ( + WTD_STATEACTION_IGNORE = 0x00000000 + WTD_STATEACTION_VERIFY = 0x00000010 + WTD_STATEACTION_CLOSE = 0x00000002 + WTD_STATEACTION_AUTO_CACHE = 0x00000003 + WTD_STATEACTION_AUTO_CACHE_FLUSH = 0x00000004 +) + +const ( + WTD_USE_IE4_TRUST_FLAG = 0x1 + WTD_NO_IE4_CHAIN_FLAG = 0x2 + WTD_NO_POLICY_USAGE_FLAG = 0x4 + WTD_REVOCATION_CHECK_NONE = 0x10 + WTD_REVOCATION_CHECK_END_CERT = 0x20 + WTD_REVOCATION_CHECK_CHAIN = 0x40 + WTD_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT = 0x80 + WTD_SAFER_FLAG = 0x100 + WTD_HASH_ONLY_FLAG = 0x200 + WTD_USE_DEFAULT_OSVER_CHECK = 0x400 + WTD_LIFETIME_SIGNING_FLAG = 0x800 + WTD_CACHE_ONLY_URL_RETRIEVAL = 0x1000 + WTD_DISABLE_MD2_MD4 = 0x2000 + WTD_MOTW = 0x4000 +) + +const ( + TRUST_E_NOSIGNATURE = 0x800B0100 + TRUST_E_EXPLICIT_DISTRUST = 0x800B0111 + TRUST_E_SUBJECT_NOT_TRUSTED = 0x800B0004 + CRYPT_E_SECURITY_SETTINGS = 0x80092026 +) + +const ( + WTD_UICONTEXT_EXECUTE = 0 + WTD_UICONTEXT_INSTALL = 1 +) + +var WINTRUST_ACTION_GENERIC_VERIFY_V2 = windows.GUID{ + Data1: 0xaac56b, + Data2: 0xcd44, + Data3: 0x11d0, + Data4: [8]byte{0x8c, 0xc2, 0x0, 0xc0, 0x4f, 0xc2, 0x95, 0xee}, +} + +type WinTrustFileInfo struct { + CbStruct uint32 + FilePath *uint16 + File windows.Handle + KnownSubject *windows.GUID +} + +type WintrustSignatureSettings struct { + CbStruct uint32 + Index uint32 + Flags uint32 + SecondarySigs uint32 + VerifiedSigIndex uint32 + CryptoPolicy *CertStrongSignPara +} + +type CertStrongSignPara struct { + CbStruct uint32 + InfoChoice uint32 + InfoOrSerializedInfoOrOID uintptr +} + +//sys WinVerifyTrust(hWnd windows.Handle, actionId *windows.GUID, data *WinTrustData) (err error) [r1 != 0] = wintrust.WinVerifyTrust -- cgit v1.2.3-59-g8ed1b