aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2019-04-30 11:41:45 +0200
committerJason A. Donenfeld <Jason@zx2c4.com>2019-04-30 20:18:46 +0200
commit4eaccce0894b32d28c44599f914a62891cb29bd1 (patch)
treeeace166b07cce61297e4231670e31d0c2106ef69
parentversion: add missing zsyscall (diff)
downloadwireguard-windows-4eaccce0894b32d28c44599f914a62891cb29bd1.tar.xz
wireguard-windows-4eaccce0894b32d28c44599f914a62891cb29bd1.zip
version: add certificate checking for official versions
This is an easy circumventable check designed mostly for convenience. Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
-rw-r--r--attacksurface.md2
-rw-r--r--ui/ui.go2
-rw-r--r--updater/downloader.go2
-rw-r--r--version/official_windows.go51
-rw-r--r--version/wintrust/certificate_windows.go58
-rw-r--r--version/wintrust/mksyscall.go2
-rw-r--r--version/wintrust/zsyscall_windows.go16
7 files changed, 118 insertions, 15 deletions
diff --git a/attacksurface.md b/attacksurface.md
index 7e881d59..e655e9fc 100644
--- a/attacksurface.md
+++ b/attacksurface.md
@@ -48,4 +48,4 @@ $ 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`.
+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, WTD_REVOKE_WHOLECHAIN)` on the MSI. If it validates, then it executes the installer with `msiexec.exe /qb- /i`.
diff --git a/ui/ui.go b/ui/ui.go
index 7cb2744a..f52e6ac3 100644
--- a/ui/ui.go
+++ b/ui/ui.go
@@ -63,7 +63,7 @@ func RunUI() {
const keepUpdaterInUnofficialBuild = true
go func() {
- if !version.IsOfficial() {
+ if !version.IsRunningOfficialVersion() {
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.")
diff --git a/updater/downloader.go b/updater/downloader.go
index c1ca4beb..382d284b 100644
--- a/updater/downloader.go
+++ b/updater/downloader.go
@@ -162,7 +162,7 @@ func DownloadVerifyAndExecute() (progress chan DownloadProgress) {
out = nil
progress <- DownloadProgress{Activity: "Verifying authenticode signature"}
- if !version.IsOfficialPath(unverifiedDestinationFilename) {
+ if !version.VerifyAuthenticode(unverifiedDestinationFilename) {
os.Remove(unverifiedDestinationFilename)
progress <- DownloadProgress{Error: errors.New("The downloaded update does not have an authentic authenticode signature")}
return
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
+}