/*
 * Rufus: The Reliable USB Formatting Utility
 * PKI functions (code signing, etc.)
 * Copyright © 2015-2024 Pete Batard <pete@akeo.ie>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/* Memory leaks detection - define _CRTDBG_MAP_ALLOC as preprocessor macro */
#ifdef _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#endif

#include <windows.h>
#include <stdio.h>
#include <wincrypt.h>
#include <wintrust.h>
#include <assert.h>

#include "rufus.h"
#include "resource.h"
#include "msapi_utf8.h"
#include "localization.h"

#define ENCODING (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING)

// MinGW doesn't seem to have this one
#if !defined(szOID_NESTED_SIGNATURE)
#define szOID_NESTED_SIGNATURE "1.3.6.1.4.1.311.2.4.1"
#endif

// Signatures names we accept. Must be the the exact name, including capitalization,
// that CertGetNameStringA(CERT_NAME_ATTR_TYPE, szOID_COMMON_NAME) returns.
const char* cert_name[3] = { "Akeo Consulting", "Akeo Systems", "Pete Batard" };
// For added security, we also validate the country code of the certificate recipient.
const char* cert_country = "IE";

typedef struct {
	LPWSTR      lpszProgramName;
	LPWSTR      lpszPublisherLink;
	LPWSTR      lpszMoreInfoLink;
} SPROG_PUBLISHERINFO, *PSPROG_PUBLISHERINFO;

// https://msdn.microsoft.com/en-us/library/ee442238.aspx
typedef struct {
	BLOBHEADER  BlobHeader;
	RSAPUBKEY   RsaHeader;
	BYTE        Modulus[256];	// 2048 bit modulus
} RSA_2048_PUBKEY;

// For PKCS7 WDAC Code Integrity processing
#define PE256_HASHSIZE  32

const GUID SKU_CODE_INTEGRITY_POLICY = { 0x976d12c8, 0xcb9f, 0x4730, { 0xbe, 0x52, 0x54, 0x60, 0x08, 0x43, 0x23, 0x8e} };

typedef struct {
	WORD        Nano;
	WORD        Micro;
	WORD        Minor;
	WORD        Major;
} CIVersion;

typedef struct {
	DWORD       PolicyFormatVersion;
	GUID        PolicyTypeGUID;
	GUID        PlatformGUID;
	DWORD       OptionFlags;
	DWORD       EKURuleEntryCount;
	DWORD       FileRuleEntryCount;
	DWORD       SignerRuleEntryCount;
	DWORD       SignerScenarioEntryCount;
	CIVersion   PolicyVersion;
	DWORD       HeaderLength;
} CIHeader;

typedef struct {
	DWORD       Type;
	DWORD       FileNameLength;
	WCHAR       FileName[0];
} CIFileRuleHeader;

typedef struct {
	DWORD       Unknown;
	CIVersion   Version;
	DWORD       HashLength;
	BYTE        Hash[0];
} CIFileRuleData;

enum {
	CI_DENY = 0,
	CI_ALLOW,
	CI_FILE_ATTRIBUTES,
};

// The RSA public key modulus for the private key we use to sign the files on the server.
// NOTE 1: This openssl modulus must be *REVERSED* to be usable with Microsoft APIs
// NOTE 2: Also, this modulus is 2052 bits, and not 2048, because openssl adds an extra
// 0x00 at the beginning to force an integer sign. These extra 8 bits *MUST* be removed.
static uint8_t rsa_pubkey_modulus[] = {
	/*
		$ openssl genrsa -aes256 -out private.pem 2048
		$ openssl rsa -in private.pem -pubout -out public.pem
		$ openssl rsa -pubin -inform PEM -text -noout < public.pem
		Public-Key: (2048 bit)
		Modulus:
			00:b6:40:7d:d1:98:7b:81:9e:be:23:0f:32:5d:55:
			60:c6:bf:b4:41:bb:43:1b:f1:e1:e6:f9:2b:d6:dd:
			11:50:e8:b9:3f:19:97:5e:a7:8b:4a:30:c6:76:58:
			72:1c:ac:ff:a1:f8:96:6c:51:5d:13:11:e3:5b:11:
			82:f5:9a:69:e4:28:97:0f:ca:1f:02:ea:1f:7d:dc:
			f9:fc:79:2f:61:ff:8e:45:60:65:ba:37:9b:de:49:
			05:6a:a8:fd:70:d0:0c:79:b6:d7:81:aa:54:c3:c6:
			4a:87:a0:45:ee:ca:d5:d5:c5:c2:ac:86:42:b3:58:
			27:d2:43:b9:37:f2:e6:75:66:17:53:d0:38:d0:c6:
			57:c2:55:36:a2:43:87:ea:24:f0:96:ec:34:dd:79:
			4d:80:54:9d:84:81:a7:cf:0c:a5:7c:d6:63:fa:7a:
			66:30:a9:50:ee:f0:e5:f8:a2:2d:ac:fc:24:21:fe:
			ef:e8:d3:6f:0e:27:b0:64:22:95:3e:6d:a6:66:97:
			c6:98:c2:47:b3:98:69:4d:b1:b5:d3:6f:43:f5:d7:
			a5:13:5e:8c:28:4f:62:4e:01:48:0a:63:89:e7:ca:
			34:aa:7d:2f:bb:70:e0:31:bb:39:49:a3:d2:c9:2e:
			a6:30:54:9a:5c:4d:58:17:d9:fc:3a:43:e6:8e:2a:
			18:e9
		Exponent: 65537 (0x10001)
	*/
	0x00, 0xb6, 0x40, 0x7d, 0xd1, 0x98, 0x7b, 0x81, 0x9e, 0xbe, 0x23, 0x0f, 0x32, 0x5d, 0x55,
	0x60, 0xc6, 0xbf, 0xb4, 0x41, 0xbb, 0x43, 0x1b, 0xf1, 0xe1, 0xe6, 0xf9, 0x2b, 0xd6, 0xdd,
	0x11, 0x50, 0xe8, 0xb9, 0x3f, 0x19, 0x97, 0x5e, 0xa7, 0x8b, 0x4a, 0x30, 0xc6, 0x76, 0x58,
	0x72, 0x1c, 0xac, 0xff, 0xa1, 0xf8, 0x96, 0x6c, 0x51, 0x5d, 0x13, 0x11, 0xe3, 0x5b, 0x11,
	0x82, 0xf5, 0x9a, 0x69, 0xe4, 0x28, 0x97, 0x0f, 0xca, 0x1f, 0x02, 0xea, 0x1f, 0x7d, 0xdc,
	0xf9, 0xfc, 0x79, 0x2f, 0x61, 0xff, 0x8e, 0x45, 0x60, 0x65, 0xba, 0x37, 0x9b, 0xde, 0x49,
	0x05, 0x6a, 0xa8, 0xfd, 0x70, 0xd0, 0x0c, 0x79, 0xb6, 0xd7, 0x81, 0xaa, 0x54, 0xc3, 0xc6,
	0x4a, 0x87, 0xa0, 0x45, 0xee, 0xca, 0xd5, 0xd5, 0xc5, 0xc2, 0xac, 0x86, 0x42, 0xb3, 0x58,
	0x27, 0xd2, 0x43, 0xb9, 0x37, 0xf2, 0xe6, 0x75, 0x66, 0x17, 0x53, 0xd0, 0x38, 0xd0, 0xc6,
	0x57, 0xc2, 0x55, 0x36, 0xa2, 0x43, 0x87, 0xea, 0x24, 0xf0, 0x96, 0xec, 0x34, 0xdd, 0x79,
	0x4d, 0x80, 0x54, 0x9d, 0x84, 0x81, 0xa7, 0xcf, 0x0c, 0xa5, 0x7c, 0xd6, 0x63, 0xfa, 0x7a,
	0x66, 0x30, 0xa9, 0x50, 0xee, 0xf0, 0xe5, 0xf8, 0xa2, 0x2d, 0xac, 0xfc, 0x24, 0x21, 0xfe,
	0xef, 0xe8, 0xd3, 0x6f, 0x0e, 0x27, 0xb0, 0x64, 0x22, 0x95, 0x3e, 0x6d, 0xa6, 0x66, 0x97,
	0xc6, 0x98, 0xc2, 0x47, 0xb3, 0x98, 0x69, 0x4d, 0xb1, 0xb5, 0xd3, 0x6f, 0x43, 0xf5, 0xd7,
	0xa5, 0x13, 0x5e, 0x8c, 0x28, 0x4f, 0x62, 0x4e, 0x01, 0x48, 0x0a, 0x63, 0x89, 0xe7, 0xca,
	0x34, 0xaa, 0x7d, 0x2f, 0xbb, 0x70, 0xe0, 0x31, 0xbb, 0x39, 0x49, 0xa3, 0xd2, 0xc9, 0x2e,
	0xa6, 0x30, 0x54, 0x9a, 0x5c, 0x4d, 0x58, 0x17, 0xd9, 0xfc, 0x3a, 0x43, 0xe6, 0x8e, 0x2a,
	0x18, 0xe9
};

/*
 * FormatMessage does not handle PKI errors
 */
const char* WinPKIErrorString(void)
{
	static char error_string[64];
	DWORD error_code = GetLastError();

	if (((error_code >> 16) != 0x8009) && ((error_code >> 16) != 0x800B))
		return WindowsErrorString();

	switch (error_code) {
	// See also https://docs.microsoft.com/en-gb/windows/desktop/com/com-error-codes-4
	case NTE_BAD_UID:
		return "Bad UID.";
	case NTE_NO_KEY:
		return "Key does not exist.";
	case NTE_BAD_KEYSET:
		return "Keyset does not exist.";
	case NTE_BAD_ALGID:
		return "Invalid algorithm specified.";
	case NTE_BAD_VER:
		return "Bad version of provider.";
	case NTE_BAD_SIGNATURE:
		return "Invalid Signature.";
	case CRYPT_E_MSG_ERROR:
		return "An error occurred while performing an operation on a cryptographic message.";
	case CRYPT_E_UNKNOWN_ALGO:
		return "Unknown cryptographic algorithm.";
	case CRYPT_E_INVALID_MSG_TYPE:
		return "Invalid cryptographic message type.";
	case CRYPT_E_HASH_VALUE:
		return "The hash value is not correct";
	case CRYPT_E_ISSUER_SERIALNUMBER:
		return "Invalid issuer and/or serial number.";
	case CRYPT_E_BAD_LEN:
		return "The length specified for the output data was insufficient.";
	case CRYPT_E_BAD_ENCODE:
		return "An error occurred during encode or decode operation.";
	case CRYPT_E_FILE_ERROR:
		return "An error occurred while reading or writing to a file.";
	case CRYPT_E_NOT_FOUND:
		return "Cannot find object or property.";
	case CRYPT_E_EXISTS:
		return "The object or property already exists.";
	case CRYPT_E_NO_PROVIDER:
		return "No provider was specified for the store or object.";
	case CRYPT_E_DELETED_PREV:
		return "The previous certificate or CRL context was deleted.";
	case CRYPT_E_NO_MATCH:
		return "Cannot find the requested object.";
	case CRYPT_E_UNEXPECTED_MSG_TYPE:
	case CRYPT_E_NO_KEY_PROPERTY:
	case CRYPT_E_NO_DECRYPT_CERT:
		return "Private key or certificate issue";
	case CRYPT_E_BAD_MSG:
		return "Not a cryptographic message.";
	case CRYPT_E_NO_SIGNER:
		return "The signed cryptographic message does not have a signer for the specified signer index.";
	case CRYPT_E_REVOKED:
		return "The certificate is revoked.";
	case CRYPT_E_NO_REVOCATION_DLL:
	case CRYPT_E_NO_REVOCATION_CHECK:
	case CRYPT_E_REVOCATION_OFFLINE:
	case CRYPT_E_NOT_IN_REVOCATION_DATABASE:
		return "Cannot check certificate revocation.";
	case CRYPT_E_INVALID_NUMERIC_STRING:
	case CRYPT_E_INVALID_PRINTABLE_STRING:
	case CRYPT_E_INVALID_IA5_STRING:
	case CRYPT_E_INVALID_X500_STRING:
	case  CRYPT_E_NOT_CHAR_STRING:
		return "Invalid string.";
	case CRYPT_E_SECURITY_SETTINGS:
		return "The cryptographic operation failed due to a local security option setting.";
	case CRYPT_E_NO_VERIFY_USAGE_CHECK:
	case CRYPT_E_VERIFY_USAGE_OFFLINE:
		return "Cannot complete usage check.";
	case CRYPT_E_NO_TRUSTED_SIGNER:
		return "None of the signers of the cryptographic message or certificate trust list is trusted.";
	case CERT_E_UNTRUSTEDROOT:
		return "The root certificate is not trusted.";
	case TRUST_E_SYSTEM_ERROR:
		return "A system-level error occurred while verifying trust.";
	case TRUST_E_NO_SIGNER_CERT:
		return "The certificate for the signer of the message is invalid or not found.";
	case TRUST_E_COUNTER_SIGNER:
		return "One of the counter signatures was invalid.";
	case TRUST_E_CERT_SIGNATURE:
		return "The signature of the certificate cannot be verified.";
	case TRUST_E_TIME_STAMP:
		return "The timestamp could not be verified.";
	case TRUST_E_BAD_DIGEST:
		return "The file content has been altered.";
	case TRUST_E_BASIC_CONSTRAINTS:
		return "A certificate's basic constraint extension has not been observed.";
	case TRUST_E_NOSIGNATURE:
		return "Not digitally signed.";
	case TRUST_E_EXPLICIT_DISTRUST:
		return "One of the certificates used was marked as untrusted by the user.";
	default:
		static_sprintf(error_string, "Unknown PKI error 0x%08lX", error_code);
		return error_string;
	}
}

// Mostly from https://support.microsoft.com/en-us/kb/323809
char* GetSignatureName(const char* path, const char* country_code, uint8_t* thumbprint, BOOL bSilent)
{
	static char szSubjectName[128];
	char szCountry[3] = "__";
	char *p = NULL, *mpath = NULL;
	int i;
	BOOL r;
	HCERTSTORE hStore = NULL;
	HCRYPTMSG hMsg = NULL;
	PCCERT_CONTEXT pCertContext = NULL;
	DWORD dwSize, dwEncoding, dwContentType, dwFormatType, dwSignerInfoSize = 0;
	PCMSG_SIGNER_INFO pSignerInfo = NULL;
	// TODO: Do we really need CertInfo? Or can we just reference pSignerInfo?
	CERT_INFO CertInfo = { 0 };
	SPROG_PUBLISHERINFO ProgPubInfo = { 0 };
	wchar_t *szFileName;

	// If the path is NULL, get the signature of the current runtime
	if (path == NULL) {
		szFileName = calloc(MAX_PATH, sizeof(wchar_t));
		if (szFileName == NULL)
			return NULL;
		dwSize = GetModuleFileNameW(NULL, szFileName, MAX_PATH);
		if ((dwSize == 0) || ((dwSize == MAX_PATH) && (GetLastError() == ERROR_INSUFFICIENT_BUFFER))) {
			uprintf("PKI: Could not get module filename: %s", WinPKIErrorString());
			goto out;
		}
		mpath = wchar_to_utf8(szFileName);
	} else {
		szFileName = utf8_to_wchar(path);
	}

	// Get message handle and store handle from the signed file.
	for (i = 0; i < 5; i++) {
		r = CryptQueryObject(CERT_QUERY_OBJECT_FILE, szFileName,
			CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, CERT_QUERY_FORMAT_FLAG_BINARY,
			0, &dwEncoding, &dwContentType, &dwFormatType, &hStore, &hMsg, NULL);
		if (r)
			break;
		if (i == 0)
			uprintf("PKI: Failed to get signature for '%s': %s", (path==NULL)?mpath:path, WinPKIErrorString());
		if (path == NULL)
			break;
		uprintf("PKI: Retrying...");
		Sleep(2000);
	}
	if (!r)
		goto out;

	// Get signer information size.
	r = CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, NULL, &dwSignerInfoSize);
	if (!r) {
		uprintf("PKI: Failed to get signer size: %s", WinPKIErrorString());
		goto out;
	}

	// Allocate memory for signer information.
	pSignerInfo = (PCMSG_SIGNER_INFO)calloc(dwSignerInfoSize, 1);
	if (!pSignerInfo) {
		uprintf("PKI: Could not allocate memory for signer information");
		goto out;
	}

	// Get Signer Information.
	r = CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, (PVOID)pSignerInfo, &dwSignerInfoSize);
	if (!r) {
		uprintf("PKI: Failed to get signer information: %s", WinPKIErrorString());
		goto out;
	}

	// Search for the signer certificate in the temporary certificate store.
	CertInfo.Issuer = pSignerInfo->Issuer;
	CertInfo.SerialNumber = pSignerInfo->SerialNumber;
	pCertContext = CertFindCertificateInStore(hStore, ENCODING, 0, CERT_FIND_SUBJECT_CERT, (PVOID)&CertInfo, NULL);
	if (!pCertContext) {
		uprintf("PKI: Failed to locate signer certificate in store: %s", WinPKIErrorString());
		goto out;
	}

	// Get the thumbprint if requested
	if (thumbprint != NULL) {
		dwSize = SHA1_HASHSIZE;
		if (!CryptHashCertificate(0, CALG_SHA1, 0, pCertContext->pbCertEncoded,
			pCertContext->cbCertEncoded, thumbprint, &dwSize)) {
			uprintf("PKI: Failed to compute the thumbprint: %s", WinPKIErrorString());
			goto out;
		}
	}

	// If a country code is provided, validate that the certificate we have is for the same country
	if (country_code != NULL) {
		dwSize = CertGetNameStringA(pCertContext, CERT_NAME_ATTR_TYPE, 0, szOID_COUNTRY_NAME,
			szCountry, sizeof(szCountry));
		if (dwSize < 2) {
			uprintf("PKI: Failed to get Country Code");
			goto out;
		}
		if (strcmpi(country_code, szCountry) != 0) {
			uprintf("PKI: Unexpected Country Code (Found '%s', expected '%s')", szCountry, country_code);
			goto out;
		}
	}

	// Isolate the signing certificate subject name
	dwSize = CertGetNameStringA(pCertContext, CERT_NAME_ATTR_TYPE, 0, szOID_COMMON_NAME,
		szSubjectName, sizeof(szSubjectName));
	if (dwSize <= 1) {
		uprintf("PKI: Failed to get Subject Name");
		goto out;
	}

	if (szCountry[0] == '_')
		suprintf("Binary executable is signed by '%s'", szSubjectName);
	else
		suprintf("Binary executable is signed by '%s' (%s)", szSubjectName, szCountry);
	p = szSubjectName;

out:
	safe_free(mpath);
	safe_free(szFileName);
	safe_free(ProgPubInfo.lpszProgramName);
	safe_free(ProgPubInfo.lpszPublisherLink);
	safe_free(ProgPubInfo.lpszMoreInfoLink);
	safe_free(pSignerInfo);
	if (pCertContext != NULL)
		CertFreeCertificateContext(pCertContext);
	if (hStore != NULL)
		CertCloseStore(hStore, 0);
	if (hMsg != NULL)
		CryptMsgClose(hMsg);
	return p;
}

// Fills the certificate's name and thumbprint.
// Tries the issuer first, and if none is available, falls back to current cert.
// Returns 0 for unsigned, -1 on error, 1 for signer or 2 for issuer.
int GetIssuerCertificateInfo(uint8_t* cert, cert_info_t* info)
{
	int ret = 0;
	DWORD dwSize, dwEncoding, dwContentType, dwFormatType, dwSignerInfoSize = 0;
	WIN_CERTIFICATE* pWinCert = (WIN_CERTIFICATE*)cert;
	CRYPT_DATA_BLOB signedDataBlob;
	HCERTSTORE hStore = NULL;
	HCRYPTMSG hMsg = NULL;
	PCMSG_SIGNER_INFO pSignerInfo = NULL;
	PCCERT_CONTEXT pCertContext[2] = { NULL, NULL };
	PCCERT_CHAIN_CONTEXT pChainContext = NULL;
	CERT_CHAIN_PARA chainPara;
	int CertIndex = 0;

	if (info == NULL)
		return -1;
	if (pWinCert == NULL || pWinCert->dwLength == 0)
		return 0;

	// Get message handle and store handle from the signed file.
	signedDataBlob.cbData = pWinCert->dwLength;
	signedDataBlob.pbData = pWinCert->bCertificate;
	if (!CryptQueryObject(CERT_QUERY_OBJECT_BLOB, &signedDataBlob,
		CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED, CERT_QUERY_FORMAT_FLAG_BINARY,
		0, &dwEncoding, &dwContentType, &dwFormatType, &hStore, &hMsg, NULL)) {
		uprintf("PKI: Failed to get signature: %s", WinPKIErrorString());
		goto out;
	}

	// Get signer information size.
	if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_CERT_INFO_PARAM, 0, NULL, &dwSignerInfoSize)) {
		uprintf("PKI: Failed to get signer size: %s", WinPKIErrorString());
		goto out;
	}

	// Allocate memory for signer information.
	pSignerInfo = (PCMSG_SIGNER_INFO)calloc(dwSignerInfoSize, 1);
	if (pSignerInfo == NULL) {
		uprintf("PKI: Could not allocate memory for signer information");
		goto out;
	}

	// Get Signer Information.
	if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_CERT_INFO_PARAM, 0, pSignerInfo, &dwSignerInfoSize)) {
		uprintf("PKI: Failed to get signer info: %s", WinPKIErrorString());
		goto out;
	}

	// Search for the signer certificate in the temporary certificate store.
	pCertContext[0] = CertFindCertificateInStore(hStore, ENCODING, 0, CERT_FIND_SUBJECT_CERT, (PVOID)pSignerInfo, NULL);
	if (pCertContext[0] == NULL) {
		uprintf("PKI: Failed to locate signer certificate in store: %s", WinPKIErrorString());
		goto out;
	}

	// Build a certificate chain to get the issuer (CA) certificate.
	memset(&chainPara, 0, sizeof(chainPara));
	chainPara.cbSize = sizeof(CERT_CHAIN_PARA);
	if (!CertGetCertificateChain(NULL, pCertContext[0], NULL, hStore, &chainPara,
		CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL | CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY, NULL, &pChainContext)) {
		uprintf("PKI: Failed to build certificate chain. Error code: %s", WinPKIErrorString());
		goto out;
	}

	// Try to get the issuer's certificate (second certificate in the chain) if available
	if (pChainContext->cChain > 0 && pChainContext->rgpChain[0]->cElement > 1) {
		pCertContext[1] = pChainContext->rgpChain[0]->rgpElement[1]->pCertContext;
		CertIndex = 1;
	}

	// Isolate the signing certificate subject name
	dwSize = CertGetNameStringA(pCertContext[CertIndex], CERT_NAME_ATTR_TYPE, 0, szOID_COMMON_NAME,
		info->name, sizeof(info->name));
	if (dwSize <= 1) {
		uprintf("PKI: Failed to get Subject Name");
		goto out;
	}

	dwSize = SHA1_HASHSIZE;
	if (!CryptHashCertificate(0, CALG_SHA1, 0, pCertContext[CertIndex]->pbCertEncoded,
		pCertContext[CertIndex]->cbCertEncoded, info->thumbprint, &dwSize)) {
		uprintf("PKI: Failed to compute the thumbprint: %s", WinPKIErrorString());
		goto out;
	}
	ret = CertIndex + 1;

out:
	safe_free(pSignerInfo);
	if (pCertContext[1] != NULL)
		CertFreeCertificateContext(pCertContext[1]);
	if (pCertContext[0] != NULL)
		CertFreeCertificateContext(pCertContext[0]);
	if (hStore != NULL)
		CertCloseStore(hStore, 0);
	if (hMsg != NULL)
		CryptMsgClose(hMsg);
	return ret;
}

// The timestamping authorities we use are RFC 3161 compliant
static uint64_t GetRFC3161TimeStamp(PCMSG_SIGNER_INFO pSignerInfo)
{
	BOOL r, found = FALSE;
	DWORD n, dwSize = 0;
	PCRYPT_CONTENT_INFO pCounterSignerInfo = NULL;
	uint64_t ts = 0ULL;
	uint8_t *timestamp_token;
	size_t timestamp_token_size;
	char* timestamp_str;
	size_t timestamp_str_size;

	// Loop through unauthenticated attributes for szOID_RFC3161_counterSign OID
	for (n = 0; n < pSignerInfo->UnauthAttrs.cAttr; n++) {
		if (lstrcmpA(pSignerInfo->UnauthAttrs.rgAttr[n].pszObjId, szOID_RFC3161_counterSign) == 0) {
			// Depending on how Microsoft implemented their timestamp checks, and the fact that we are dealing
			// with UnauthAttrs, there's a possibility that an attacker may add a "fake" RFC 3161 countersigner
			// to try to trick us into using their timestamp data. Detect that.
			if (found) {
				uprintf("PKI: Multiple RFC 3161 countersigners found. This could indicate something very nasty...");
				return 0ULL;
			}
			found = TRUE;

			// Read the countersigner message data
			r = CryptDecodeObjectEx(PKCS_7_ASN_ENCODING, PKCS_CONTENT_INFO,
				pSignerInfo->UnauthAttrs.rgAttr[n].rgValue[0].pbData,
				pSignerInfo->UnauthAttrs.rgAttr[n].rgValue[0].cbData,
				CRYPT_DECODE_ALLOC_FLAG, NULL, (PVOID)&pCounterSignerInfo, &dwSize);
			if (!r) {
				uprintf("PKI: Could not retrieve RFC 3161 countersigner data: %s", WinPKIErrorString());
				continue;
			}

			// Get the RFC 3161 timestamp message
			timestamp_token = get_data_from_asn1(pCounterSignerInfo->Content.pbData,
				pCounterSignerInfo->Content.cbData, szOID_TIMESTAMP_TOKEN,
				// 0x04 = "Octet String" ASN.1 tag
				0x04, &timestamp_token_size);
			if (timestamp_token) {
				timestamp_str = get_data_from_asn1(timestamp_token, timestamp_token_size, NULL,
					// 0x18 = "Generalized Time" ASN.1 tag
					0x18, &timestamp_str_size);
				if (timestamp_str) {
					// As per RFC 3161 The syntax is: YYYYMMDDhhmmss[.s...]Z
					if ((timestamp_str_size < 14) || (timestamp_str[timestamp_str_size - 1] != 'Z')) {
						// Sanity checks
						uprintf("PKI: Not an RFC 3161 timestamp");
						DumpBufferHex(timestamp_str, timestamp_str_size);
					} else {
						ts = strtoull(timestamp_str, NULL, 10);
					}
				}
			}
			LocalFree(pCounterSignerInfo);
		}
	}
	return ts;
}

// The following is used to get the RFP 3161 timestamp of a nested signature
static uint64_t GetNestedRFC3161TimeStamp(PCMSG_SIGNER_INFO pSignerInfo)
{
	BOOL r, found = FALSE;
	DWORD n, dwSize = 0;
	PCRYPT_CONTENT_INFO pNestedSignature = NULL;
	PCMSG_SIGNER_INFO pNestedSignerInfo = NULL;
	HCRYPTMSG hMsg = NULL;
	uint64_t ts = 0ULL;

	// Loop through unauthenticated attributes for szOID_NESTED_SIGNATURE OID
	for (n = 0; ; n++) {
		if (pNestedSignature != NULL) {
			LocalFree(pNestedSignature);
			pNestedSignature = NULL;
		}
		if (hMsg != NULL) {
			CryptMsgClose(hMsg);
			hMsg = NULL;
		}
		safe_free(pNestedSignerInfo);
		if (n >= pSignerInfo->UnauthAttrs.cAttr)
			break;
		if (lstrcmpA(pSignerInfo->UnauthAttrs.rgAttr[n].pszObjId, szOID_NESTED_SIGNATURE) == 0) {
			if (found) {
				uprintf("PKI: Multiple nested signatures found. This could indicate something very nasty...");
				return 0ULL;
			}
			found = TRUE;
			r = CryptDecodeObjectEx(PKCS_7_ASN_ENCODING, PKCS_CONTENT_INFO,
				pSignerInfo->UnauthAttrs.rgAttr[n].rgValue[0].pbData,
				pSignerInfo->UnauthAttrs.rgAttr[n].rgValue[0].cbData,
				CRYPT_DECODE_ALLOC_FLAG, NULL, (PVOID)&pNestedSignature, &dwSize);
			if (!r) {
				uprintf("PKI: Could not retrieve nested signature data: %s", WinPKIErrorString());
				continue;
			}

			hMsg = CryptMsgOpenToDecode(ENCODING, CMSG_DETACHED_FLAG, CMSG_SIGNED, (HCRYPTPROV)NULL, NULL, NULL);
			if (hMsg == NULL) {
				uprintf("PKI: Could not create nested signature message: %s", WinPKIErrorString());
				continue;
			}
			r = CryptMsgUpdate(hMsg, pNestedSignature->Content.pbData, pNestedSignature->Content.cbData, TRUE);
			if (!r) {
				uprintf("PKI: Could not update message: %s", WinPKIErrorString());
				continue;
			}
			// Get nested signer
			r = CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, NULL, &dwSize);
			if (!r) {
				uprintf("PKI: Failed to get nested signer size: %s", WinPKIErrorString());
				continue;
			}
			pNestedSignerInfo = (PCMSG_SIGNER_INFO)calloc(dwSize, 1);
			if (!pNestedSignerInfo) {
				uprintf("PKI: Could not allocate memory for nested signer");
				continue;
			}
			r = CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, (PVOID)pNestedSignerInfo, &dwSize);
			if (!r) {
				uprintf("PKI: Failed to get nested signer information: %s", WinPKIErrorString());
				continue;
			}
			ts = GetRFC3161TimeStamp(pNestedSignerInfo);
		}
	}
	return ts;
}

// Return the signature timestamp (as a YYYYMMDDHHMMSS value) or 0 on error
uint64_t GetSignatureTimeStamp(const char* path)
{
	char *mpath = NULL;
	BOOL r;
	HMODULE hm;
	HCERTSTORE hStore = NULL;
	HCRYPTMSG hMsg = NULL;
	DWORD dwSize, dwEncoding, dwContentType, dwFormatType, dwSignerInfoSize = 0;
	PCMSG_SIGNER_INFO pSignerInfo = NULL;
	wchar_t *szFileName;
	uint64_t timestamp = 0ULL, nested_timestamp;

	// If the path is NULL, get the signature of the current runtime
	if (path == NULL) {
		szFileName = calloc(MAX_PATH, sizeof(wchar_t));
		if (szFileName == NULL)
			goto out;
		hm = GetModuleHandle(NULL);
		if (hm == NULL) {
			uprintf("PKI: Could not get current executable handle: %s", WinPKIErrorString());
			goto out;
		}
		dwSize = GetModuleFileNameW(hm, szFileName, MAX_PATH);
		if ((dwSize == 0) || ((dwSize == MAX_PATH) && (GetLastError() == ERROR_INSUFFICIENT_BUFFER))) {
			uprintf("PKI: Could not get module filename: %s", WinPKIErrorString());
			goto out;
		}
		mpath = wchar_to_utf8(szFileName);
	} else {
		szFileName = utf8_to_wchar(path);
	}

	// Get message handle and store handle from the signed file.
	r = CryptQueryObject(CERT_QUERY_OBJECT_FILE, szFileName,
		CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, CERT_QUERY_FORMAT_FLAG_BINARY,
		0, &dwEncoding, &dwContentType, &dwFormatType, &hStore, &hMsg, NULL);
	if (!r) {
		uprintf("PKI: Failed to get signature for '%s': %s", (path==NULL)?mpath:path, WinPKIErrorString());
		goto out;
	}

	// Get signer information size.
	r = CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, NULL, &dwSignerInfoSize);
	if (!r) {
		uprintf("PKI: Failed to get signer size: %s", WinPKIErrorString());
		goto out;
	}

	// Allocate memory for signer information.
	pSignerInfo = (PCMSG_SIGNER_INFO)calloc(dwSignerInfoSize, 1);
	if (!pSignerInfo) {
		uprintf("PKI: Could not allocate memory for signer information");
		goto out;
	}

	// Get Signer Information.
	r = CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, (PVOID)pSignerInfo, &dwSignerInfoSize);
	if (!r) {
		uprintf("PKI: Failed to get signer information: %s", WinPKIErrorString());
		goto out;
	}

	// Get the RFC 3161 timestamp
	timestamp = GetRFC3161TimeStamp(pSignerInfo);
	if (timestamp)
		uprintf("Note: '%s' has timestamp %s", (path==NULL)?mpath:path, TimestampToHumanReadable(timestamp));
	// Because we were using both SHA-1 and SHA-256 signatures during the SHA-256 transition, we were
	// in the very specific situation where Windows could say that our executable passed Authenticode
	// validation even if the SHA-1 signature or timestamps had been altered.
	// This means that, unless we also check the nested signature timestamp, an attacker could alter
	// the most vulnerable signature (which may also be the one used for chronology validation) and
	// trick us into using an invalid timestamp value. To prevent this, we validate that, if we have
	// both a regular and nested timestamp, they are within 60 seconds of each other.
	// Even as we are no longer dual signing with two versions of SHA, we keep the code in case a
	// major SHA-256 vulnerability is found and we have to go through a dual SHA again.
	nested_timestamp = GetNestedRFC3161TimeStamp(pSignerInfo);
	if (nested_timestamp)
		uprintf("Note: '%s' has nested timestamp %s", (path==NULL)?mpath:path, TimestampToHumanReadable(nested_timestamp));
	if ((timestamp != 0ULL) && (nested_timestamp != 0ULL)) {
		if (_abs64(nested_timestamp - timestamp) > 100) {
			uprintf("PKI: Signature timestamp (%lld) and nested timestamp (%lld) differ by more than a minute. "
				"This could indicate something very nasty...", timestamp, nested_timestamp);
			timestamp = 0ULL;
		}
	}

out:
	safe_free(mpath);
	safe_free(szFileName);
	safe_free(pSignerInfo);
	if (hStore != NULL)
		CertCloseStore(hStore, 0);
	if (hMsg != NULL)
		CryptMsgClose(hMsg);
	return timestamp;
}

// From https://msdn.microsoft.com/en-us/library/windows/desktop/aa382384.aspx
LONG ValidateSignature(HWND hDlg, const char* path)
{
	LONG r = TRUST_E_SYSTEM_ERROR;
	WINTRUST_DATA trust_data = { 0 };
	WINTRUST_FILE_INFO trust_file = { 0 };
	GUID guid_generic_verify =	// WINTRUST_ACTION_GENERIC_VERIFY_V2
		{ 0xaac56b, 0xcd44, 0x11d0,{ 0x8c, 0xc2, 0x0, 0xc0, 0x4f, 0xc2, 0x95, 0xee } };
	char *signature_name;
	size_t i;
	uint64_t current_ts, update_ts;

	// Check the signature name. Make it specific enough (i.e. don't simply check for "Akeo")
	// so that, besides hacking our server, it'll place an extra hurdle on any malicious entity
	// into also fooling a C.A. to issue a certificate that passes our test.
	signature_name = GetSignatureName(path, cert_country, NULL, (hDlg == INVALID_HANDLE_VALUE));
	if (signature_name == NULL) {
		uprintf("PKI: Could not get signature name");
		if (hDlg != INVALID_HANDLE_VALUE)
			Notification(MB_OK | MB_ICONERROR, lmprintf(MSG_283), lmprintf(MSG_284));
		return TRUST_E_NOSIGNATURE;
	}
	for (i = 0; i < ARRAYSIZE(cert_name); i++) {
		if (strcmp(signature_name, cert_name[i]) == 0)
			break;
	}
	if (i >= ARRAYSIZE(cert_name)) {
		uprintf("PKI: Signature '%s' is unexpected...", signature_name);
		if ((hDlg == INVALID_HANDLE_VALUE) || (Notification(MB_YESNO | MB_ICONWARNING,
			lmprintf(MSG_283), lmprintf(MSG_285, signature_name)) != IDYES))
			return TRUST_E_EXPLICIT_DISTRUST;
	}

	trust_file.cbStruct = sizeof(trust_file);
	trust_file.pcwszFilePath = utf8_to_wchar(path);
	if (trust_file.pcwszFilePath == NULL) {
		uprintf("PKI: Unable to convert '%s' to UTF16", path);
		return RUFUS_ERROR(ERROR_NOT_ENOUGH_MEMORY);
	}

	trust_data.cbStruct = sizeof(trust_data);
	// NB: WTD_UI_ALL can result in ERROR_SUCCESS even if the signature validation fails,
	// because it still prompts the user to run untrusted software, even after explicitly
	// notifying them that the signature invalid (and of course Microsoft had to make
	// that UI prompt a bit too similar to the other benign prompt you get when running
	// trusted software, which, as per cert.org's assessment, may confuse non-security
	// conscious-users who decide to gloss over these kind of notifications).
	trust_data.dwUIChoice = WTD_UI_NONE;
	// We just downloaded from the Internet, so we should be able to check revocation
	trust_data.fdwRevocationChecks = WTD_REVOKE_WHOLECHAIN;
	// 0x400 = WTD_MOTW  for Windows 8.1 or later
	trust_data.dwProvFlags = WTD_REVOCATION_CHECK_CHAIN | 0x400;
	trust_data.dwUnionChoice = WTD_CHOICE_FILE;
	trust_data.pFile = &trust_file;

	// NB: Calling this API will create DLL sideloading issues through 'msasn1.dll'.
	// So make sure you delay-load 'wintrust.dll' in your application.
	r = WinVerifyTrustEx(INVALID_HANDLE_VALUE, &guid_generic_verify, &trust_data);
	safe_free(trust_file.pcwszFilePath);
	switch (r) {
	case ERROR_SUCCESS:
		// hDlg = INVALID_HANDLE_VALUE is used when validating the Fido PS1 script
		if (hDlg == INVALID_HANDLE_VALUE)
			break;
		// Verify that the timestamp of the downloaded update is in the future of our current one.
		// This is done to prevent the use of an officially signed, but older binary, as potential attack vector.
		current_ts = GetSignatureTimeStamp(NULL);
		if (current_ts == 0ULL) {
			uprintf("PKI: Cannot retrieve the current binary's timestamp - Aborting update");
			r = TRUST_E_TIME_STAMP;
		} else {
			update_ts = GetSignatureTimeStamp(path);
			if (update_ts < current_ts) {
				uprintf("PKI: Update timestamp (%" PRIi64 ") is younger than ours (%" PRIi64 ") - Aborting update", update_ts, current_ts);
				r = TRUST_E_TIME_STAMP;
			}
		}
		if ((r != ERROR_SUCCESS) && (force_update < 2))
			Notification(MB_OK | MB_ICONERROR, lmprintf(MSG_299), lmprintf(MSG_300));
		break;
	case TRUST_E_NOSIGNATURE:
		// Should already have been reported, but since we have a custom message for it...
		uprintf("PKI: File does not appear to be signed: %s", WinPKIErrorString());
		if (hDlg != INVALID_HANDLE_VALUE)
			Notification(MB_OK | MB_ICONERROR, lmprintf(MSG_283), lmprintf(MSG_284));
		break;
	default:
		uprintf("PKI: Failed to validate signature: %s", WinPKIErrorString());
		if (hDlg != INVALID_HANDLE_VALUE)
			Notification(MB_OK | MB_ICONERROR, lmprintf(MSG_283), lmprintf(MSG_240));
		break;
	}

	return r;
}

// Why-oh-why am I the only one on github doing this openssl vs MS signature validation?!?
// For once, I'd like to find code samples from *OTHER PEOPLE* who went through this ordeal first...
BOOL ValidateOpensslSignature(BYTE* pbBuffer, DWORD dwBufferLen, BYTE* pbSignature, DWORD dwSigLen)
{
	HCRYPTPROV hProv = 0;
	HCRYPTHASH hHash = 0;
	HCRYPTKEY hPubKey;
	// We could load and convert an openssl PEM, but since we know what we need...
	RSA_2048_PUBKEY pbMyPubKey = {
		{ PUBLICKEYBLOB, CUR_BLOB_VERSION, 0, CALG_RSA_KEYX },
		// $ openssl genrsa -aes256 -out private.pem 2048
		// Generating RSA private key, 2048 bit long modulus
		// e is 65537 (0x010001)
		// => 0x010001 below. Also 0x31415352 = "RSA1"
		{ 0x31415352, sizeof(pbMyPubKey.Modulus) * 8, 0x010001 },
		{ 0 }	// Modulus is initialized below
	};
	USHORT dwMyPubKeyLen = sizeof(pbMyPubKey);
	BOOL r;
	BYTE t;
	int i, j;

	// Get a handle to the default PROV_RSA_AES provider (AES so we get SHA-256 support).
	// 2 passes in case we need to create a new container.
	r = CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_NEWKEYSET | CRYPT_VERIFYCONTEXT);
	if (!r) {
		uprintf("PKI: Could not create the default key container: %s", WinPKIErrorString());
		goto out;
	}

	// Reverse the modulus bytes from openssl (and also remove the extra unwanted 0x00)
	assert(sizeof(rsa_pubkey_modulus) >= sizeof(pbMyPubKey.Modulus));
	for (i = 0; i < sizeof(pbMyPubKey.Modulus); i++)
		pbMyPubKey.Modulus[i] = rsa_pubkey_modulus[sizeof(rsa_pubkey_modulus) -1 - i];

	// Import our RSA public key so that the MS API can use it
	r = CryptImportKey(hProv, (BYTE*)&pbMyPubKey.BlobHeader, dwMyPubKeyLen, 0, 0, &hPubKey);
	if (!r) {
		uprintf("PKI: Could not import public key: %s", WinPKIErrorString());
		goto out;
	}

	// Create the hash object.
	r = CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash);
	if (!r) {
		uprintf("PKI: Could not create empty hash: %s", WinPKIErrorString());
		goto out;
	}

	// Compute the cryptographic hash of the buffer.
	r = CryptHashData(hHash, pbBuffer, dwBufferLen, 0);
	if (!r) {
		uprintf("PKI: Could not hash data: %s", WinPKIErrorString());
		goto out;
	}

	// Reverse the signature bytes
	for (i = 0, j = dwSigLen - 1; i < j; i++, j--) {
		t = pbSignature[i];
		pbSignature[i] = pbSignature[j];
		pbSignature[j] = t;
	}

	// Now that we have all of the public key, hash and signature data in a
	// format that Microsoft can handle, we can call CryptVerifySignature().
	r = CryptVerifySignature(hHash, pbSignature, dwSigLen, hPubKey, NULL, 0);
	if (!r) {
		// If the signature is invalid, clear the buffer so that
		// we don't keep potentially nasty stuff in memory.
		memset(pbBuffer, 0, dwBufferLen);
		uprintf("Signature validation failed: %s", WinPKIErrorString());
	}

out:
	if (hHash)
		CryptDestroyHash(hHash);
	if (hProv)
		CryptReleaseContext(hProv, 0);
	return r;
}

// The following SkuSiPolicy.p7b parsing code is derived from:
// https://gist.github.com/mattifestation/92e545bf1ee5b68eeb71d254cec2f78e
// by Matthew Graeber, with contributions by James Forshaw.
BOOL ParseSKUSiPolicy(void)
{
	char path[MAX_PATH];
	wchar_t* wpath = NULL;
	BOOL r = FALSE;
	DWORD i, dwEncoding, dwContentType, dwFormatType;
	DWORD dwPolicySize = 0, dwBaseIndex = 0, dwSizeCount;
	HCRYPTMSG hMsg = NULL;
	CRYPT_DATA_BLOB pkcsData = { 0 };
	DWORD* pdwEkuRules;
	BYTE* pbRule;
	CIHeader* Header;
	CIFileRuleHeader* FileRuleHeader;
	CIFileRuleData* FileRuleData;

	pe256ssp_size = 0;
	safe_free(pe256ssp);
	// Must use sysnative for WOW
	static_sprintf(path, "%s\\SecureBootUpdates\\SKUSiPolicy.p7b", sysnative_dir);
	wpath = utf8_to_wchar(path);
	if (wpath == NULL)
		goto out;

	r = CryptQueryObject(CERT_QUERY_OBJECT_FILE, wpath, CERT_QUERY_CONTENT_FLAG_ALL,
		CERT_QUERY_FORMAT_FLAG_ALL, 0, &dwEncoding, &dwContentType, &dwFormatType, NULL,
		&hMsg, NULL);
	if (!r || dwContentType != CERT_QUERY_CONTENT_PKCS7_SIGNED)
		goto out;

	r = CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, NULL, &pkcsData.cbData);
	if (!r || pkcsData.cbData == 0) {
		uprintf("ParseSKUSiPolicy: Failed to retreive CMSG_CONTENT_PARAM size: %s", WindowsErrorString());
		goto out;
	}
	pkcsData.pbData = malloc(pkcsData.cbData);
	if (pkcsData.pbData == NULL)
		goto out;
	r = CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, pkcsData.pbData, &pkcsData.cbData);
	if (!r) {
		uprintf("ParseSKUSiPolicy: Failed to retreive CMSG_CONTENT_PARAM: %s", WindowsErrorString());
		goto out;
	}

	// Now process the actual Security Policy content
	if (pkcsData.pbData[0] == 4) {
		dwPolicySize = pkcsData.pbData[1];
		dwBaseIndex = 2;
		if ((dwPolicySize & 0x80) == 0x80) {
			dwSizeCount = dwPolicySize & 0x7F;
			dwBaseIndex += dwSizeCount;
			dwPolicySize = 0;
			for (i = 0; i < dwSizeCount; i++) {
				dwPolicySize = dwPolicySize << 8;
				dwPolicySize = dwPolicySize | pkcsData.pbData[2 + i];
			}
		}
	}

	// Sanity checks
	Header = (CIHeader*)&pkcsData.pbData[dwBaseIndex];
	if (Header->HeaderLength + sizeof(uint32_t) != sizeof(CIHeader)) {
		uprintf("ParseSKUSiPolicy: Unexpected Code Integrity Header size (0x%02x)", Header->HeaderLength);
		goto out;
	}
	if (!CompareGUID(&Header->PolicyTypeGUID, &SKU_CODE_INTEGRITY_POLICY)) {
		uprintf("ParseSKUSiPolicy: Unexpected Policy Type GUID %s", GuidToString(&Header->PolicyTypeGUID, TRUE));
		goto out;
	}

	// Skip the EKU Rules
	pdwEkuRules = (DWORD*) &pkcsData.pbData[dwBaseIndex + sizeof(CIHeader)];
	for (i = 0; i < Header->EKURuleEntryCount; i++)
		pdwEkuRules = &pdwEkuRules[(*pdwEkuRules + (2 * sizeof(DWORD) - 1)) / sizeof(DWORD)];

	// Process the Files Rules
	pbRule = (BYTE*)pdwEkuRules;
	pe256ssp = malloc(Header->FileRuleEntryCount * PE256_HASHSIZE);
	if (pe256ssp == NULL)
		goto out;
	for (i = 0; i < Header->FileRuleEntryCount; i++) {
		FileRuleHeader = (CIFileRuleHeader*)pbRule;
		pbRule = &pbRule[sizeof(CIFileRuleHeader)];
		if (FileRuleHeader->FileNameLength != 0) {
//			uprintf("%S", FileRuleHeader->FileName);
			pbRule = &pbRule[((FileRuleHeader->FileNameLength + sizeof(DWORD) - 1) / sizeof(DWORD)) * sizeof(DWORD)];
		}
		FileRuleData = (CIFileRuleData*)pbRule;
		if (FileRuleData->HashLength > 0x80) {
			uprintf("ParseSKUSiPolicy: Unexpected hash length for entry %d (0x%02x)", i, FileRuleData->HashLength);
			// We're probably screwed, so bail out
			break;
		}
		//  We are only interested in 'DENY' type with PE256 hashes
		if (FileRuleHeader->Type == CI_DENY && FileRuleData->HashLength == PE256_HASHSIZE) {
			// Microsoft has the bad habit of duplicating entries - only add a hash if it's different from previous entry
			if ((pe256ssp_size == 0) ||
				(memcmp(&pe256ssp[(pe256ssp_size - 1) * PE256_HASHSIZE], FileRuleData->Hash, PE256_HASHSIZE) != 0)) {
				memcpy(&pe256ssp[pe256ssp_size * PE256_HASHSIZE], FileRuleData->Hash, PE256_HASHSIZE);
				pe256ssp_size++;
			}
		}
		pbRule = &pbRule[sizeof(CIFileRuleData) + ((FileRuleData->HashLength + sizeof(DWORD) - 1) / sizeof(DWORD)) * sizeof(DWORD)];
	}

	r = TRUE;

out:
	if (hMsg != NULL)
		CryptMsgClose(hMsg);
	free(pkcsData.pbData);
	free(wpath);
	return r;
}
