aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/version
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2020-11-30 17:34:55 +0100
committerJason A. Donenfeld <Jason@zx2c4.com>2020-11-30 22:07:27 +0100
commit163beba470f71cd6f68dc17cd9b7fa0035945f25 (patch)
tree66a7ebd3bfb78c77b85672ebd093c2741ba69f05 /version
parentupdater: another attempt at winhttp (diff)
downloadwireguard-windows-163beba470f71cd6f68dc17cd9b7fa0035945f25.tar.xz
wireguard-windows-163beba470f71cd6f68dc17cd9b7fa0035945f25.zip
version: use crypt32 instead of go x509 for cn extraction for file size
Another attempt at trying to remove an asn1 parser. Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
Diffstat (limited to 'version')
-rw-r--r--version/official_windows.go47
-rw-r--r--version/wintrust/certificate_test.go42
-rw-r--r--version/wintrust/certificate_windows.go141
-rw-r--r--version/wintrust/zsyscall_windows.go27
4 files changed, 207 insertions, 50 deletions
diff --git a/version/official_windows.go b/version/official_windows.go
index 2ca33b43..12b95e3b 100644
--- a/version/official_windows.go
+++ b/version/official_windows.go
@@ -6,11 +6,11 @@
package version
import (
- "encoding/asn1"
"os"
"unsafe"
"golang.org/x/sys/windows"
+
"golang.zx2c4.com/wireguard/windows/version/wintrust"
)
@@ -20,16 +20,6 @@ const (
policyExtensionOid = "2.5.29.32"
)
-type policyQualifierInfo struct {
- PolicyQualifierId asn1.ObjectIdentifier
- Qualifier asn1.RawValue
-}
-
-type policyInformation struct {
- Policy asn1.ObjectIdentifier
- Qualifiers []policyQualifierInfo `asn1:"optional"`
-}
-
func VerifyAuthenticode(path string) bool {
path16, err := windows.UTF16PtrFromString(path)
if err != nil {
@@ -50,23 +40,21 @@ func VerifyAuthenticode(path string) bool {
return wintrust.WinVerifyTrust(windows.InvalidHandle, &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.
+// These are easily by-passable checks, which do not serve serve security purposes. Do not place security-sensitive
+// functions below this line.
+
func IsRunningOfficialVersion() bool {
path, err := os.Executable()
if err != nil {
return false
}
- // This is easily circumvented. We don't even verify the chain before hand with WinVerifyTrust.
- // False certificates can be appended. But that's okay, as this isn't security related.
-
- certs, err := wintrust.ExtractCertificates(path)
+ names, err := wintrust.ExtractCertificateNames(path)
if err != nil {
return false
}
- for _, cert := range certs {
- if cert.Subject.CommonName == officialCommonName {
+ for _, name := range names {
+ if name == officialCommonName {
return true
}
}
@@ -79,26 +67,13 @@ func IsRunningEVSigned() bool {
return false
}
- // This is easily circumvented. We don't even verify the chain before hand with WinVerifyTrust.
- // False certificates can be appended. But that's okay, as this isn't security related.
-
- certs, err := wintrust.ExtractCertificates(path)
+ policies, err := wintrust.ExtractCertificatePolicies(path, policyExtensionOid)
if err != nil {
return false
}
- for _, cert := range certs {
- for _, extension := range cert.Extensions {
- if extension.Id.String() == policyExtensionOid {
- var policies []policyInformation
- if _, err = asn1.Unmarshal(extension.Value, &policies); err != nil {
- continue
- }
- for _, policy := range policies {
- if policy.Policy.String() == evPolicyOid {
- return true
- }
- }
- }
+ for _, policy := range policies {
+ if policy == evPolicyOid {
+ return true
}
}
return false
diff --git a/version/wintrust/certificate_test.go b/version/wintrust/certificate_test.go
new file mode 100644
index 00000000..86d90526
--- /dev/null
+++ b/version/wintrust/certificate_test.go
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019-2020 WireGuard LLC. All Rights Reserved.
+ */
+
+package wintrust
+
+import (
+ "fmt"
+ "path/filepath"
+ "testing"
+
+ "golang.org/x/sys/windows"
+)
+
+func TestExtractCertificateNames(t *testing.T) {
+ system32, err := windows.GetSystemDirectory()
+ if err != nil {
+ t.Fatal(err)
+ }
+ names, err := ExtractCertificateNames(filepath.Join(system32, "ntoskrnl.exe"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ for i, name := range names {
+ fmt.Printf("%d: %s\n", i, name)
+ }
+}
+
+func TestExtractCertificateExtension(t *testing.T) {
+ system32, err := windows.GetSystemDirectory()
+ if err != nil {
+ t.Fatal(err)
+ }
+ policies, err := ExtractCertificatePolicies(filepath.Join(system32, "ntoskrnl.exe"), "2.5.29.32")
+ if err != nil {
+ t.Fatal(err)
+ }
+ for i, policy := range policies {
+ fmt.Printf("%d: %s\n", i, policy)
+ }
+}
diff --git a/version/wintrust/certificate_windows.go b/version/wintrust/certificate_windows.go
index 2f4249ef..0ce905e9 100644
--- a/version/wintrust/certificate_windows.go
+++ b/version/wintrust/certificate_windows.go
@@ -6,7 +6,6 @@
package wintrust
import (
- "crypto/x509"
"syscall"
"unsafe"
@@ -17,23 +16,129 @@ const (
_CERT_QUERY_OBJECT_FILE = 1
_CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED = 1024
_CERT_QUERY_FORMAT_FLAG_ALL = 14
+ _CERT_NAME_SIMPLE_DISPLAY_TYPE = 4
)
+type blob struct {
+ len uint32
+ data *byte
+}
+
+type bitBlob struct {
+ len uint32
+ data *byte
+ unusedBits uint32
+}
+
+type algoIdentifier struct {
+ objId uintptr
+ params blob
+}
+
+type pubkeyInfo struct {
+ algo algoIdentifier
+ publicKey bitBlob
+}
+
+type certExtension struct {
+ objId *byte
+ critical uint32
+ value blob
+}
+
+type certInfo struct {
+ version uint32
+ serialNumber blob /* CRYPT_INTEGER_BLOB */
+ signatureAlgorithm algoIdentifier /* CRYPT_ALGORITHM_IDENTIFIER */
+ issuer blob /* CERT_NAME_BLOB */
+ notbefore windows.Filetime
+ notafter windows.Filetime
+ subject blob /* CERT_NAME_BLOB */
+ subjectPublicKeyInfo pubkeyInfo /* CERT_PUBLIC_KEY_INFO */
+ issuerUniqueId bitBlob /* CRYPT_BIT_BLOB */
+ subjectUniqueId bitBlob /* CRYPT_BIT_BLOB */
+ countExtensions uint32
+ extensions *certExtension /* *CERT_EXTENSION */
+}
+
+type certPolicy struct {
+ identifier *byte
+ countQualifiers uint32
+ qualifiers uintptr /* CERT_POLICY_QUALIFIER_INFO */
+}
+
+type certPoliciesInfo struct {
+ countPolicyInfos uint32
+ policyInfos *certPolicy
+}
+
//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
+//sys certGetNameString(certContext *windows.CertContext, nameType uint32, flags uint32, typePara unsafe.Pointer, name *uint16, size uint32) (chars uint32) = crypt32.CertGetNameStringW
+//sys certFindExtension(objId *byte, countExtensions uint32, extensions *certExtension) (ret *certExtension) = crypt32.CertFindExtension
+//sys cryptDecodeObject(encodingType uint32, structType *byte, encodedBytes *byte, lenEncodedBytes uint32, flags uint32, decoded unsafe.Pointer, decodedLen *uint32) (err error) = crypt32.CryptDecodeObject
+
+func ExtractCertificateNames(path string) ([]string, 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 cert *windows.CertContext
+ var names []string
+ 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
+ }
+ nameLen := certGetNameString(cert, _CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nil, nil, 0)
+ if nameLen == 0 {
+ continue
+ }
+ name16 := make([]uint16, nameLen)
+ if certGetNameString(cert, _CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nil, &name16[0], nameLen) != nameLen {
+ continue
+ }
+ if name16[0] == 0 {
+ continue
+ }
+ names = append(names, windows.UTF16ToString(name16))
+ }
+ if names == nil {
+ return nil, syscall.Errno(windows.CRYPT_E_NOT_FOUND)
+ }
+ return names, nil
+}
-func ExtractCertificates(path string) ([]x509.Certificate, error) {
+func ExtractCertificatePolicies(path string, oid string) ([]string, error) {
path16, err := windows.UTF16PtrFromString(path)
if err != nil {
return nil, err
}
+ oid8, err := windows.BytePtrFromString(oid)
+ 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
+ var policies []string
for {
cert, err = windows.CertEnumCertificatesInStore(certStore, cert)
if err != nil {
@@ -47,17 +152,29 @@ func ExtractCertificates(path string) ([]x509.Certificate, error) {
if cert == nil {
break
}
- buf := make([]byte, cert.Length)
- //TODO: when Go 1.16 or 1.17 comes out, switch to:
- // copy(buf, unsafe.Slice(unsafe.Pointer(cert.EncodedCert), len(buf)))
- for i := range buf {
- buf[i] = *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(cert.EncodedCert)) + uintptr(i)))
+ ci := (*certInfo)(unsafe.Pointer(cert.CertInfo))
+ ext := certFindExtension(oid8, ci.countExtensions, ci.extensions)
+ if ext == nil {
+ continue
}
- if c, err := x509.ParseCertificate(buf); err == nil {
- certs = append(certs, *c)
- } else {
+ var decodedLen uint32
+ err = cryptDecodeObject(windows.X509_ASN_ENCODING|windows.PKCS_7_ASN_ENCODING, ext.objId, ext.value.data, ext.value.len, 0, nil, &decodedLen)
+ if err != nil {
return nil, err
}
+ bytes := make([]byte, decodedLen)
+ certPoliciesInfo := (*certPoliciesInfo)(unsafe.Pointer(&bytes[0]))
+ err = cryptDecodeObject(windows.X509_ASN_ENCODING|windows.PKCS_7_ASN_ENCODING, ext.objId, ext.value.data, ext.value.len, 0, unsafe.Pointer(&bytes[0]), &decodedLen)
+ if err != nil {
+ return nil, err
+ }
+ for i := uintptr(0); i < uintptr(certPoliciesInfo.countPolicyInfos); i++ {
+ cp := (*certPolicy)(unsafe.Pointer(uintptr(unsafe.Pointer(certPoliciesInfo.policyInfos)) + i*unsafe.Sizeof(*certPoliciesInfo.policyInfos)))
+ policies = append(policies, windows.BytePtrToString(cp.identifier))
+ }
+ }
+ if policies == nil {
+ return nil, syscall.Errno(windows.CRYPT_E_NOT_FOUND)
}
- return certs, nil
+ return policies, nil
}
diff --git a/version/wintrust/zsyscall_windows.go b/version/wintrust/zsyscall_windows.go
index 69d42775..67daccb1 100644
--- a/version/wintrust/zsyscall_windows.go
+++ b/version/wintrust/zsyscall_windows.go
@@ -41,10 +41,33 @@ var (
modcrypt32 = windows.NewLazySystemDLL("crypt32.dll")
modwintrust = windows.NewLazySystemDLL("wintrust.dll")
- procCryptQueryObject = modcrypt32.NewProc("CryptQueryObject")
- procWinVerifyTrust = modwintrust.NewProc("WinVerifyTrust")
+ procCertFindExtension = modcrypt32.NewProc("CertFindExtension")
+ procCertGetNameStringW = modcrypt32.NewProc("CertGetNameStringW")
+ procCryptDecodeObject = modcrypt32.NewProc("CryptDecodeObject")
+ procCryptQueryObject = modcrypt32.NewProc("CryptQueryObject")
+ procWinVerifyTrust = modwintrust.NewProc("WinVerifyTrust")
)
+func certFindExtension(objId *byte, countExtensions uint32, extensions *certExtension) (ret *certExtension) {
+ r0, _, _ := syscall.Syscall(procCertFindExtension.Addr(), 3, uintptr(unsafe.Pointer(objId)), uintptr(countExtensions), uintptr(unsafe.Pointer(extensions)))
+ ret = (*certExtension)(unsafe.Pointer(r0))
+ return
+}
+
+func certGetNameString(certContext *windows.CertContext, nameType uint32, flags uint32, typePara unsafe.Pointer, name *uint16, size uint32) (chars uint32) {
+ r0, _, _ := syscall.Syscall6(procCertGetNameStringW.Addr(), 6, uintptr(unsafe.Pointer(certContext)), uintptr(nameType), uintptr(flags), uintptr(typePara), uintptr(unsafe.Pointer(name)), uintptr(size))
+ chars = uint32(r0)
+ return
+}
+
+func cryptDecodeObject(encodingType uint32, structType *byte, encodedBytes *byte, lenEncodedBytes uint32, flags uint32, decoded unsafe.Pointer, decodedLen *uint32) (err error) {
+ r1, _, e1 := syscall.Syscall9(procCryptDecodeObject.Addr(), 7, uintptr(encodingType), uintptr(unsafe.Pointer(structType)), uintptr(unsafe.Pointer(encodedBytes)), uintptr(lenEncodedBytes), uintptr(flags), uintptr(decoded), uintptr(unsafe.Pointer(decodedLen)), 0, 0)
+ if r1 == 0 {
+ err = errnoErr(e1)
+ }
+ 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 {