Bring back Burn's implementation of signature verification.
partial #6447
Sean Hall committed
May 3, 2021 at 14:41 UTC
2f4287fdcee83b30e0f7f3ce548bcdff2ee85e1f
6 files changed
+164
-3
src/burn/engine/cache.cpp
+131
@@ -17,6 +17,11 @@ static LPWSTR vsczDefaultUserPackageCache = NULL;
17
static LPWSTR vsczDefaultMachinePackageCache = NULL;
18
static LPWSTR vsczCurrentMachinePackageCache = NULL;
19
20
+static HRESULT CacheVerifyPayloadSignature(
21
+ __in BURN_PAYLOAD* pPayload,
22
+ __in_z LPCWSTR wzUnverifiedPayloadPath,
23
+ __in HANDLE hFile
24
+ );
25
static HRESULT CalculateWorkingFolder(
26
__in_z LPCWSTR wzBundleId,
27
__deref_out_z LPWSTR* psczWorkingFolder
@@ -131,6 +136,10 @@ static HRESULT VerifyHash(
136
__in LPPROGRESS_ROUTINE pfnProgress,
137
__in LPVOID pContext
138
);
139
+static HRESULT VerifyPayloadAgainstCertChain(
140
+ __in BURN_PAYLOAD* pPayload,
141
+ __in PCCERT_CHAIN_CONTEXT pChainContext
142
+ );
143
static HRESULT SendCacheBeginMessage(
144
__in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler,
145
__in LPVOID pContext,
@@ -1133,6 +1142,56 @@ LExit:
1142
return hr;
1143
}
1144
1145
+static HRESULT CacheVerifyPayloadSignature(
1146
+ __in BURN_PAYLOAD* pPayload,
1147
+ __in_z LPCWSTR wzUnverifiedPayloadPath,
1148
+ __in HANDLE hFile
1149
+ )
1150
+{
1151
+ HRESULT hr = S_OK;
1152
+ LONG er = ERROR_SUCCESS;
1153
+
1154
+ GUID guidAuthenticode = WINTRUST_ACTION_GENERIC_VERIFY_V2;
1155
+ WINTRUST_FILE_INFO wfi = { };
1156
+ WINTRUST_DATA wtd = { };
1157
+ CRYPT_PROVIDER_DATA* pProviderData = NULL;
1158
+ CRYPT_PROVIDER_SGNR* pSigner = NULL;
1159
+
1160
+ // Verify the payload assuming online.
1161
+ wfi.cbStruct = sizeof(wfi);
1162
+ wfi.pcwszFilePath = wzUnverifiedPayloadPath;
1163
+ wfi.hFile = hFile;
1164
+
1165
+ wtd.cbStruct = sizeof(wtd);
1166
+ wtd.dwUnionChoice = WTD_CHOICE_FILE;
1167
+ wtd.pFile = &wfi;
1168
+ wtd.dwStateAction = WTD_STATEACTION_VERIFY;
1169
+ wtd.dwProvFlags = WTD_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT;
1170
+ wtd.dwUIChoice = WTD_UI_NONE;
1171
+
1172
+ er = ::WinVerifyTrust(static_cast<HWND>(INVALID_HANDLE_VALUE), &guidAuthenticode, &wtd);
1173
+ if (er)
1174
+ {
1175
+ // Verify the payload assuming offline.
1176
+ wtd.dwProvFlags |= WTD_CACHE_ONLY_URL_RETRIEVAL;
1177
+
1178
+ er = ::WinVerifyTrust(static_cast<HWND>(INVALID_HANDLE_VALUE), &guidAuthenticode, &wtd);
1179
+ ExitOnWin32Error(er, hr, "Failed authenticode verification of payload: %ls", wzUnverifiedPayloadPath);
1180
+ }
1181
+
1182
+ pProviderData = ::WTHelperProvDataFromStateData(wtd.hWVTStateData);
1183
+ ExitOnNullWithLastError(pProviderData, hr, "Failed to get provider state from authenticode certificate.");
1184
+
1185
+ pSigner = ::WTHelperGetProvSignerFromChain(pProviderData, 0, FALSE, 0);
1186
+ ExitOnNullWithLastError(pSigner, hr, "Failed to get signer chain from authenticode certificate.");
1187
+
1188
+ hr = VerifyPayloadAgainstCertChain(pPayload, pSigner->pChainContext);
1189
+ ExitOnFailure(hr, "Failed to verify expected payload against actual certificate chain.");
1190
+
1191
+LExit:
1192
+ return hr;
1193
+}
1194
+
1195
extern "C" void CacheCleanup(
1196
__in BOOL fPerMachine,
1197
__in_z LPCWSTR wzBundleId
@@ -1563,6 +1622,10 @@ static HRESULT VerifyThenTransferPayload(
1622
1623
switch (pPayload->verification)
1624
{
1625
+ case BURN_PAYLOAD_VERIFICATION_AUTHENTICODE:
1626
+ hr = CacheVerifyPayloadSignature(pPayload, wzUnverifiedPayloadPath, hFile);
1627
+ ExitOnFailure(hr, "Failed to verify payload signature: %ls", wzCachedPath);
1628
+ break;
1629
case BURN_PAYLOAD_VERIFICATION_HASH:
1630
hr = VerifyHash(pPayload->pbHash, pPayload->cbHash, pPayload->qwFileSize, TRUE, wzUnverifiedPayloadPath, hFile, BURN_CACHE_STEP_HASH, pfnCacheMessageHandler, pfnProgress, pContext);
1631
ExitOnFailure(hr, "Failed to verify payload hash: %ls", wzCachedPath);
@@ -1705,6 +1768,10 @@ static HRESULT VerifyFileAgainstPayload(
1768
1769
switch (pPayload->verification)
1770
{
1771
+ case BURN_PAYLOAD_VERIFICATION_AUTHENTICODE:
1772
+ hr = CacheVerifyPayloadSignature(pPayload, wzVerifyPath, hFile);
1773
+ ExitOnFailure(hr, "Failed to verify signature of payload: %ls", pPayload->sczKey);
1774
+ break;
1775
case BURN_PAYLOAD_VERIFICATION_HASH:
1776
fVerifyFileSize = TRUE;
1777
@@ -2144,6 +2211,70 @@ LExit:
2211
return hr;
2212
}
2213
2214
+static HRESULT VerifyPayloadAgainstCertChain(
2215
+ __in BURN_PAYLOAD* pPayload,
2216
+ __in PCCERT_CHAIN_CONTEXT pChainContext
2217
+ )
2218
+{
2219
+ HRESULT hr = S_OK;
2220
+ PCCERT_CONTEXT pChainElementCertContext = NULL;
2221
+
2222
+ BYTE rgbPublicKeyIdentifier[SHA1_HASH_LEN] = { };
2223
+ DWORD cbPublicKeyIdentifier = sizeof(rgbPublicKeyIdentifier);
2224
+ BYTE* pbThumbprint = NULL;
2225
+ DWORD cbThumbprint = 0;
2226
+
2227
+ // Walk up the chain looking for a certificate in the chain that matches our expected public key identifier
2228
+ // and thumbprint (if a thumbprint was provided).
2229
+ HRESULT hrChainVerification = E_NOTFOUND; // assume we won't find a match.
2230
+ for (DWORD i = 0; i < pChainContext->rgpChain[0]->cElement; ++i)
2231
+ {
2232
+ pChainElementCertContext = pChainContext->rgpChain[0]->rgpElement[i]->pCertContext;
2233
+
2234
+ // Get the certificate's public key identifier.
2235
+ if (!::CryptHashPublicKeyInfo(NULL, CALG_SHA1, 0, X509_ASN_ENCODING, &pChainElementCertContext->pCertInfo->SubjectPublicKeyInfo, rgbPublicKeyIdentifier, &cbPublicKeyIdentifier))
2236
+ {
2237
+ ExitWithLastError(hr, "Failed to get certificate public key identifier.");
2238
+ }
2239
+
2240
+ // Compare the certificate's public key identifier with the payload's public key identifier. If they
2241
+ // match, we're one step closer to the a positive result.
2242
+ if (pPayload->cbCertificateRootPublicKeyIdentifier == cbPublicKeyIdentifier &&
2243
+ 0 == memcmp(pPayload->pbCertificateRootPublicKeyIdentifier, rgbPublicKeyIdentifier, cbPublicKeyIdentifier))
2244
+ {
2245
+ // If the payload specified a thumbprint for the certificate, verify it.
2246
+ if (pPayload->pbCertificateRootThumbprint)
2247
+ {
2248
+ hr = CertReadProperty(pChainElementCertContext, CERT_SHA1_HASH_PROP_ID, &pbThumbprint, &cbThumbprint);
2249
+ ExitOnFailure(hr, "Failed to read certificate thumbprint.");
2250
+
2251
+ if (pPayload->cbCertificateRootThumbprint == cbThumbprint &&
2252
+ 0 == memcmp(pPayload->pbCertificateRootThumbprint, pbThumbprint, cbThumbprint))
2253
+ {
2254
+ // If we got here, we found that our payload public key identifier and thumbprint
2255
+ // matched an element in the certficate chain.
2256
+ hrChainVerification = S_OK;
2257
+ break;
2258
+ }
2259
+
2260
+ ReleaseNullMem(pbThumbprint);
2261
+ }
2262
+ else // no thumbprint match necessary so we're good to go.
2263
+ {
2264
+ hrChainVerification = S_OK;
2265
+ break;
2266
+ }
2267
+ }
2268
+ }
2269
+ hr = hrChainVerification;
2270
+ ExitOnFailure(hr, "Failed to find expected public key in certificate chain.");
2271
+
2272
+LExit:
2273
+ ReleaseMem(pbThumbprint);
2274
+
2275
+ return hr;
2276
+}
2277
+
2278
static HRESULT SendCacheBeginMessage(
2279
__in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler,
2280
__in LPVOID pContext,
src/burn/engine/payload.cpp
+24
@@ -132,6 +132,28 @@ extern "C" HRESULT PayloadsParseFromXml(
132
fValidFileSize = TRUE;
133
}
134
135
+ // @CertificateAuthorityKeyIdentifier
136
+ hr = XmlGetAttributeEx(pixnNode, L"CertificateRootPublicKeyIdentifier", &scz);
137
+ if (E_NOTFOUND != hr)
138
+ {
139
+ ExitOnFailure(hr, "Failed to get @CertificateRootPublicKeyIdentifier.");
140
+
141
+ hr = StrAllocHexDecode(scz, &pPayload->pbCertificateRootPublicKeyIdentifier, &pPayload->cbCertificateRootPublicKeyIdentifier);
142
+ ExitOnFailure(hr, "Failed to hex decode @CertificateRootPublicKeyIdentifier.");
143
+
144
+ pPayload->verification = BURN_PAYLOAD_VERIFICATION_AUTHENTICODE;
145
+ }
146
+
147
+ // @CertificateThumbprint
148
+ hr = XmlGetAttributeEx(pixnNode, L"CertificateRootThumbprint", &scz);
149
+ if (E_NOTFOUND != hr)
150
+ {
151
+ ExitOnFailure(hr, "Failed to get @CertificateRootThumbprint.");
152
+
153
+ hr = StrAllocHexDecode(scz, &pPayload->pbCertificateRootThumbprint, &pPayload->cbCertificateRootThumbprint);
154
+ ExitOnFailure(hr, "Failed to hex decode @CertificateRootThumbprint.");
155
+ }
156
+
157
// @Hash
158
hr = XmlGetAttributeEx(pixnNode, L"Hash", &scz);
159
if (E_NOTFOUND != hr)
@@ -191,6 +213,8 @@ extern "C" void PayloadUninitialize(
213
ReleaseStr(pPayload->sczKey);
214
ReleaseStr(pPayload->sczFilePath);
215
ReleaseMem(pPayload->pbHash);
216
+ ReleaseMem(pPayload->pbCertificateRootThumbprint);
217
+ ReleaseMem(pPayload->pbCertificateRootPublicKeyIdentifier);
218
ReleaseStr(pPayload->sczSourcePath);
219
ReleaseStr(pPayload->sczLocalFilePath);
220
ReleaseStr(pPayload->downloadSource.sczUrl);
src/burn/engine/payload.h
+5
@@ -26,6 +26,7 @@ enum BURN_PAYLOAD_STATE
26
enum BURN_PAYLOAD_VERIFICATION
27
{
28
BURN_PAYLOAD_VERIFICATION_NONE,
29
+ BURN_PAYLOAD_VERIFICATION_AUTHENTICODE,
30
BURN_PAYLOAD_VERIFICATION_HASH,
31
BURN_PAYLOAD_VERIFICATION_UPDATE_BUNDLE,
32
};
@@ -41,6 +42,10 @@ typedef struct _BURN_PAYLOAD
42
DWORD64 qwFileSize;
43
LPWSTR sczFilePath; // file path relative to the execute location
44
45
+ BYTE* pbCertificateRootPublicKeyIdentifier;
46
+ DWORD cbCertificateRootPublicKeyIdentifier;
47
+ BYTE* pbCertificateRootThumbprint;
48
+ DWORD cbCertificateRootThumbprint;
49
BYTE* pbHash;
50
DWORD cbHash;
51
BURN_PAYLOAD_VERIFICATION verification;
src/burn/engine/precomp.h
+1
@@ -28,6 +28,7 @@
28
#include <apputil.h>
29
#include <buffutil.h>
30
#include <cabutil.h>
31
+#include <certutil.h>
32
#include <cryputil.h>
33
#include <dirutil.h>
34
#include <fileutil.h>
src/burn/stub/stub.vcxproj
+2
-2
@@ -56,14 +56,14 @@
56
57
<PropertyGroup>
58
<ProjectAdditionalIncludeDirectories>$(ProjectDir)..\engine\inc</ProjectAdditionalIncludeDirectories>
59
- <ProjectAdditionalLinkLibraries>cabinet.lib;crypt32.lib;msi.lib;rpcrt4.lib;shlwapi.lib;wininet.lib;wuguid.lib;engine.res</ProjectAdditionalLinkLibraries>
59
+ <ProjectAdditionalLinkLibraries>cabinet.lib;crypt32.lib;msi.lib;rpcrt4.lib;shlwapi.lib;wininet.lib;wintrust.lib;wuguid.lib;engine.res</ProjectAdditionalLinkLibraries>
60
</PropertyGroup>
61
62
<ItemDefinitionGroup>
63
<Link>
64
<SwapRunFromCD>true</SwapRunFromCD>
65
<SwapRunFromNET>true</SwapRunFromNET>
66
- <DelayLoadDLLs>cabinet.dll;crypt32.dll;msi.dll;shlwapi.dll;version.dll;wininet.dll</DelayLoadDLLs>
66
+ <DelayLoadDLLs>cabinet.dll;crypt32.dll;msi.dll;shlwapi.dll;version.dll;wininet.dll;wintrust.dll</DelayLoadDLLs>
67
</Link>
68
</ItemDefinitionGroup>
69
src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj
+1
-1
@@ -40,7 +40,7 @@
40
41
<PropertyGroup>
42
<ProjectAdditionalIncludeDirectories>$(ProjectAdditionalIncludeDirectories);..\..\engine;..\..\..\api\burn\WixToolset.BootstrapperCore.Native\inc;..\..\..\libs\dutil\WixToolset.Dutil\inc</ProjectAdditionalIncludeDirectories>
43
- <ProjectAdditionalLinkLibraries>cabinet.lib;crypt32.lib;msi.lib;rpcrt4.lib;shlwapi.lib;wininet.lib;$(RootBuildFolder)libs\$(Configuration)\$(PlatformToolset)\$(PlatformTarget)\dutil.lib</ProjectAdditionalLinkLibraries>
43
+ <ProjectAdditionalLinkLibraries>cabinet.lib;crypt32.lib;msi.lib;rpcrt4.lib;shlwapi.lib;wininet.lib;wintrust.lib;$(RootBuildFolder)libs\$(Configuration)\$(PlatformToolset)\$(PlatformTarget)\dutil.lib</ProjectAdditionalLinkLibraries>
44
</PropertyGroup>
45
46
<ItemGroup>