@joebigelow / wix / commits / 2f4287fd

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>