mirror of
https://github.com/pbatard/rufus.git
synced 2024-08-14 23:57:05 +00:00
* Not sure if we'll use this to hook into FfuCaptureImage()/FfuApplyImage()/FfuMountImage() directly. But at least, if we ever need it, it's there...
911 lines
33 KiB
C
911 lines
33 KiB
C
/*
|
|
* Rufus: The Reliable USB Formatting Utility
|
|
* PKI functions (code signing, etc.)
|
|
* Copyright © 2015-2023 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, 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;
|
|
PCMSG_SIGNER_INFO pSignerInfo = NULL;
|
|
DWORD dwSignerInfo = 0;
|
|
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, &dwSignerInfo);
|
|
if (!r) {
|
|
uprintf("PKI: Failed to get signer size: %s", WinPKIErrorString());
|
|
goto out;
|
|
}
|
|
|
|
// Allocate memory for signer information.
|
|
pSignerInfo = (PCMSG_SIGNER_INFO)calloc(dwSignerInfo, 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, &dwSignerInfo);
|
|
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 temporary store: %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;
|
|
}
|
|
|
|
// 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, ×tamp_token_size);
|
|
if (timestamp_token) {
|
|
timestamp_str = get_data_from_asn1(timestamp_token, timestamp_token_size, NULL,
|
|
// 0x18 = "Generalized Time" ASN.1 tag
|
|
0x18, ×tamp_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;
|
|
PCMSG_SIGNER_INFO pSignerInfo = NULL;
|
|
DWORD dwSignerInfo = 0;
|
|
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, &dwSignerInfo);
|
|
if (!r) {
|
|
uprintf("PKI: Failed to get signer size: %s", WinPKIErrorString());
|
|
goto out;
|
|
}
|
|
|
|
// Allocate memory for signer information.
|
|
pSignerInfo = (PCMSG_SIGNER_INFO)calloc(dwSignerInfo, 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, &dwSignerInfo);
|
|
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 and nested timestamp 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, (hDlg == INVALID_HANDLE_VALUE));
|
|
if (signature_name == NULL) {
|
|
uprintf("PKI: Could not get signature name");
|
|
if (hDlg != INVALID_HANDLE_VALUE)
|
|
MessageBoxExU(hDlg, lmprintf(MSG_284), lmprintf(MSG_283), MB_OK | MB_ICONERROR | MB_IS_RTL, selected_langid);
|
|
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) || (MessageBoxExU(hDlg,
|
|
lmprintf(MSG_285, signature_name), lmprintf(MSG_283),
|
|
MB_YESNO | MB_ICONWARNING | MB_IS_RTL, selected_langid) != 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 ERROR_SEVERITY_ERROR | FAC(FACILITY_CERT) | 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))
|
|
MessageBoxExU(hDlg, lmprintf(MSG_300), lmprintf(MSG_299), MB_OK | MB_ICONERROR | MB_IS_RTL, selected_langid);
|
|
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)
|
|
MessageBoxExU(hDlg, lmprintf(MSG_284), lmprintf(MSG_283), MB_OK | MB_ICONERROR | MB_IS_RTL, selected_langid);
|
|
break;
|
|
default:
|
|
uprintf("PKI: Failed to validate signature: %s", WinPKIErrorString());
|
|
if (hDlg != INVALID_HANDLE_VALUE)
|
|
MessageBoxExU(hDlg, lmprintf(MSG_240), lmprintf(MSG_283), MB_OK | MB_ICONERROR | MB_IS_RTL, selected_langid);
|
|
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;
|
|
}
|