From 163beba470f71cd6f68dc17cd9b7fa0035945f25 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Mon, 30 Nov 2020 17:34:55 +0100 Subject: 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 --- version/official_windows.go | 47 +++-------- version/wintrust/certificate_test.go | 42 ++++++++++ version/wintrust/certificate_windows.go | 141 +++++++++++++++++++++++++++++--- version/wintrust/zsyscall_windows.go | 27 +++++- 4 files changed, 207 insertions(+), 50 deletions(-) create mode 100644 version/wintrust/certificate_test.go (limited to 'version') 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 { -- cgit v1.2.3-59-g8ed1b