@joebigelow / wix-1 / commits / 1e21a5de

Use bundle's default CacheId instead of hash in certificate CacheId.

Sean Hall committed Jul 1, 2022 at 10:30 UTC 1e21a5de35a9d12218065e999f172ea766ffd3df
9 files changed +188 -50
src/wix/WixToolset.Core.Burn/Bundles/CacheIdGenerator.cs
+61 -13
@@ -14,32 +14,80 @@ namespace WixToolset.Core.Burn.Bundles
14 private const int ReasonableCountOfCharsFromCertificateThumbprint = 20;
15 private const int ReasonableUpperLimitForCacheId = 64;
16
17 - public static string GenerateCacheIdFromPayloadHashAndThumbprint(WixBundlePayloadSymbol payloadSymbol)
17 + public static string GenerateLocalCacheId(IMessaging messaging, IntermediateSymbol harvestedPackageSymbol, WixBundlePayloadSymbol payloadSymbol, SourceLineNumber sourceLineNumbers, string elementName)
18 {
19 + string cacheId = null;
20 +
21 + // If we are validating the package via certificate,
22 + // the CacheId must be specified in source code.
23 + if (!String.IsNullOrEmpty(payloadSymbol.CertificatePublicKey) || !String.IsNullOrEmpty(payloadSymbol.CertificateThumbprint))
24 + {
25 + var oneOfCertificateAttributeNames = !String.IsNullOrEmpty(payloadSymbol.CertificatePublicKey) ? "CertificatePublicKey" : "CertificateThumbprint";
26 +
27 + messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, elementName, "CacheId", oneOfCertificateAttributeNames));
28 + }
29 + else
30 + {
31 + cacheId = GenerateDefaultCacheId(harvestedPackageSymbol, payloadSymbol, out _);
32 + }
33 +
34 + return cacheId;
35 + }
36 +
37 + public static string GenerateRemoteCacheId(IntermediateSymbol harvestedPackageSymbol, WixBundlePayloadSymbol payloadSymbol)
38 + {
39 + // If we are not validating the package via certificate,
40 + // the CacheId can be generated at build time.
41 + if (String.IsNullOrEmpty(payloadSymbol.CertificateThumbprint))
42 + {
43 + return null;
44 + }
45 +
46 + var defaultCacheId = GenerateDefaultCacheId(harvestedPackageSymbol, payloadSymbol, out var canTruncate);
47 var takeFromThumbprint = Math.Min(ReasonableCountOfCharsFromCertificateThumbprint, payloadSymbol.CertificateThumbprint.Length);
20 - var takeFromHash = Math.Min(ReasonableUpperLimitForCacheId - takeFromThumbprint, payloadSymbol.Hash.Length);
48 + var takeFromDefault = Math.Min(ReasonableUpperLimitForCacheId - takeFromThumbprint, defaultCacheId.Length);
49
22 - return payloadSymbol.Hash.Substring(0, takeFromHash) + payloadSymbol.CertificateThumbprint.Substring(0, takeFromThumbprint);
50 + var defaultPart = !canTruncate ? defaultCacheId : defaultCacheId.Substring(0, takeFromDefault);
51 + var certificatePart = payloadSymbol.CertificateThumbprint.Substring(0, takeFromThumbprint);
52 + return defaultPart + certificatePart;
53 }
54
25 - public static string GenerateCacheIdFromPackagePayloadHash(IMessaging messaging, WixBundlePayloadSymbol packagePayload, string elementName)
55 + public static string GenerateDefaultCacheId(IntermediateSymbol harvestedPackageSymbol, WixBundlePayloadSymbol payloadSymbol, out bool canTruncate)
56 {
27 - string cacheId = null;
57 + string cacheId;
58 + canTruncate = false;
59
29 - // If we are validating the package via certificate, the CacheId must be specified
30 - // in source code.
31 - if (!String.IsNullOrEmpty(packagePayload.CertificatePublicKey) || !String.IsNullOrEmpty(packagePayload.CertificateThumbprint))
60 + if (harvestedPackageSymbol is WixBundleHarvestedBundlePackageSymbol harvestedBundlePackageSymbol)
61 {
33 - var oneOfCertificateAttributeNames = !String.IsNullOrEmpty(packagePayload.CertificatePublicKey) ? "CertificatePublicKey" : "CertificateThumbprint";
34 -
35 - messaging.Write(ErrorMessages.ExpectedAttribute(packagePayload.SourceLineNumbers, elementName, "CacheId", oneOfCertificateAttributeNames));
62 + cacheId = GenerateCacheIdFromGuidAndVersion(harvestedBundlePackageSymbol.BundleId, harvestedBundlePackageSymbol.Version);
63 + }
64 + else if (harvestedPackageSymbol is WixBundleHarvestedMsiPackageSymbol harvestedMsiPackageSymbol)
65 + {
66 + cacheId = GenerateCacheIdFromGuidAndVersion(harvestedMsiPackageSymbol.ProductCode, harvestedMsiPackageSymbol.ProductVersion);
67 + }
68 + else if (harvestedPackageSymbol is WixBundleHarvestedMspPackageSymbol harvestedMspPackageSymbol)
69 + {
70 + cacheId = harvestedMspPackageSymbol.PatchCode;
71 }
37 - else // validating package by hashing, so the CacheId can be defaulted to the hash with a "reasonable" upper limit since the CacheId is in the cached file path.
72 + else
73 {
39 - cacheId = packagePayload.Hash.Length > ReasonableUpperLimitForCacheId ? packagePayload.Hash.Substring(0, ReasonableUpperLimitForCacheId) : packagePayload.Hash;
74 + // No inherent id is available, so use the hash.
75 + cacheId = GenerateCacheIdFromHash(payloadSymbol.Hash);
76 + canTruncate = true;
77 }
78
79 return cacheId;
80 }
81 +
82 + public static string GenerateCacheIdFromGuidAndVersion(string guid, string version)
83 + {
84 + return String.Format("{0}v{1}", guid, version);
85 + }
86 +
87 + public static string GenerateCacheIdFromHash(string hash)
88 + {
89 + // The hash needs to be truncated to a "reasonable" upper limit since the CacheId is in the cached file path.
90 + return hash.Length > ReasonableUpperLimitForCacheId ? hash.Substring(0, ReasonableUpperLimitForCacheId) : hash;
91 + }
92 }
93 }
src/wix/WixToolset.Core.Burn/Bundles/ProcessBundlePackageCommand.cs
+1 -1
@@ -94,7 +94,7 @@ namespace WixToolset.Core.Burn.Bundles
94
95 if (String.IsNullOrEmpty(this.ChainPackage.CacheId))
96 {
97 - this.ChainPackage.CacheId = String.Format("{0}v{1}", this.BundlePackage.BundleId, this.BundlePackage.Version);
97 + this.ChainPackage.CacheId = CacheIdGenerator.GenerateLocalCacheId(this.Messaging, harvestedBundlePackage, this.PackagePayload, this.BundlePackage.SourceLineNumbers, "BundlePackage");
98 }
99
100 if (String.IsNullOrEmpty(this.ChainPackage.DisplayName))
src/wix/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs
+1 -1
@@ -35,7 +35,7 @@ namespace WixToolset.Core.Burn.Bundles
35
36 if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId))
37 {
38 - this.Facade.PackageSymbol.CacheId = CacheIdGenerator.GenerateCacheIdFromPackagePayloadHash(this.Messaging, packagePayload, "ExePackage");
38 + this.Facade.PackageSymbol.CacheId = CacheIdGenerator.GenerateLocalCacheId(this.Messaging, null, packagePayload, this.Facade.PackageSymbol.SourceLineNumbers, "ExePackage");
39 }
40
41 this.Facade.PackageSymbol.Version = packagePayload.Version;
src/wix/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs
+1 -1
@@ -100,7 +100,7 @@ namespace WixToolset.Core.Burn.Bundles
100
101 if (String.IsNullOrEmpty(this.ChainPackage.CacheId))
102 {
103 - this.ChainPackage.CacheId = String.Format("{0}v{1}", this.MsiPackage.ProductCode, this.MsiPackage.ProductVersion);
103 + this.ChainPackage.CacheId = CacheIdGenerator.GenerateLocalCacheId(this.Messaging, harvestedMsiPackage, this.PackagePayload, this.MsiPackage.SourceLineNumbers, "MsiPackage");
104 }
105
106 if (String.IsNullOrEmpty(this.ChainPackage.DisplayName))
src/wix/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs
+1 -1
@@ -82,7 +82,7 @@ namespace WixToolset.Core.Burn.Bundles
82
83 if (String.IsNullOrEmpty(this.ChainPackage.CacheId))
84 {
85 - this.ChainPackage.CacheId = this.MspPackage.PatchCode;
85 + this.ChainPackage.CacheId = CacheIdGenerator.GenerateLocalCacheId(this.Messaging, harvestedMspPackage, this.PackagePayload, this.MspPackage.SourceLineNumbers, "MspPackage");
86 }
87 }
88
src/wix/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs
+1 -1
@@ -32,7 +32,7 @@ namespace WixToolset.Core.Burn.Bundles
32
33 if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId))
34 {
35 - this.Facade.PackageSymbol.CacheId = CacheIdGenerator.GenerateCacheIdFromPackagePayloadHash(this.Messaging, packagePayload, "MsuPackage");
35 + this.Facade.PackageSymbol.CacheId = CacheIdGenerator.GenerateLocalCacheId(this.Messaging, null, packagePayload, this.Facade.PackageSymbol.SourceLineNumbers, "MsuPackage");
36 }
37
38 this.Facade.PackageSymbol.PerMachine = true; // MSUs are always per-machine.
src/wix/WixToolset.Core.Burn/CommandLine/RemotePayloadSubcommand.cs
+44 -31
@@ -26,6 +26,7 @@ namespace WixToolset.Core.Burn.CommandLine
26 private static readonly XName ExePackagePayloadName = "ExePackagePayload";
27 private static readonly XName MsuPackagePayloadName = "MsuPackagePayload";
28 private static readonly XName PayloadName = "Payload";
29 + private static readonly XName PayloadGroupName = "PayloadGroup";
30 private static readonly XName RemoteBundleName = "RemoteBundle";
31 private static readonly XName RemoteRelatedBundleName = "RemoteRelatedBundle";
32
@@ -192,42 +193,31 @@ namespace WixToolset.Core.Burn.CommandLine
193 private XElement HarvestPackageElement(IEnumerable<string> paths)
194 {
195 var harvestedFiles = this.HarvestRemotePayloads(paths).ToList();
196 + var firstFile = harvestedFiles.FirstOrDefault();
197
196 - XElement element;
197 -
198 - switch (harvestedFiles[0].PackageType)
198 + if (firstFile == null)
199 {
200 - case WixBundlePackageType.Bundle:
201 - element = new XElement(BundlePackageName);
202 - break;
203 -
204 - case WixBundlePackageType.Exe:
205 - element = new XElement(ExePackageName);
206 - break;
207 -
208 - case WixBundlePackageType.Msu:
209 - element = new XElement(MsuPackageName);
210 - break;
211 -
212 - default:
213 - return null;
200 + return null;
201 }
202
216 - var packagePayloadFile = harvestedFiles.FirstOrDefault();
203 + var containerElement = firstFile.PackageElement;
204
218 - if (packagePayloadFile != null)
205 + if (containerElement == null)
206 + {
207 + containerElement = new XElement(PayloadGroupName);
208 + }
209 + else
210 {
220 - if (packagePayloadFile.Element.Attribute("CertificateThumbprint") != null)
211 + var cacheId = CacheIdGenerator.GenerateRemoteCacheId(firstFile.HarvestedPackageSymbol, firstFile.PayloadSymbol);
212 + if (cacheId != null)
213 {
222 - var cacheId = CacheIdGenerator.GenerateCacheIdFromPayloadHashAndThumbprint(packagePayloadFile.PayloadSymbol);
223 -
224 - element.Add(new XAttribute("CacheId", cacheId));
214 + containerElement.Add(new XAttribute("CacheId", cacheId));
215 }
226 -
227 - element.Add(harvestedFiles.Select(h => h.Element));
216 }
217
230 - return element;
218 + containerElement.Add(harvestedFiles.Select(h => h.Element));
219 +
220 + return containerElement;
221 }
222
223 private IEnumerable<HarvestedFile> HarvestRemotePayloads(IEnumerable<string> paths)
@@ -363,14 +353,19 @@ namespace WixToolset.Core.Burn.CommandLine
353 var harvestedFile = new HarvestedFile
354 {
355 Element = element,
366 - PackageType = packageType,
356 PayloadSymbol = payloadSymbol,
357 };
358
359 switch (packageType)
360 {
361 case WixBundlePackageType.Bundle:
373 - this.HarvestBundle(harvestedFile);
362 + this.HarvestBundlePackage(harvestedFile);
363 + break;
364 + case WixBundlePackageType.Exe:
365 + this.HarvestExePackage(harvestedFile);
366 + break;
367 + case WixBundlePackageType.Msu:
368 + this.HarvestMsuPackage(harvestedFile);
369 break;
370 }
371
@@ -390,7 +385,7 @@ namespace WixToolset.Core.Burn.CommandLine
385 return Path.GetFileName(path);
386 }
387
393 - private void HarvestBundle(HarvestedFile harvestedFile)
388 + private void HarvestBundlePackage(HarvestedFile harvestedFile)
389 {
390 var packagePayloadSymbol = new WixBundleBundlePackagePayloadSymbol(null, new Identifier(AccessModifier.Section, harvestedFile.PayloadSymbol.Id.Id))
391 {
@@ -448,16 +443,34 @@ namespace WixToolset.Core.Burn.CommandLine
443 }
444
445 harvestedFile.PackagePayloads.AddRange(command.Payloads);
451 -
446 + harvestedFile.HarvestedPackageSymbol = command.HarvestedBundlePackage;
447 harvestedFile.Element.Add(bundleElement);
448 +
449 + harvestedFile.PackageElement = new XElement(BundlePackageName);
450 + if (BurnCommon.BurnV3Namespace == command.HarvestedBundlePackage.ManifestNamespace)
451 + {
452 + harvestedFile.PackageElement.Add(new XAttribute("Visible", "yes"));
453 + }
454 }
455 }
456
457 + private void HarvestExePackage(HarvestedFile harvestedFile)
458 + {
459 + harvestedFile.PackageElement = new XElement(ExePackageName);
460 + }
461 +
462 + private void HarvestMsuPackage(HarvestedFile harvestedFile)
463 + {
464 + harvestedFile.PackageElement = new XElement(MsuPackageName);
465 + }
466 +
467 private class HarvestedFile
468 {
469 public XElement Element { get; set; }
470
460 - public WixBundlePackageType? PackageType { get; internal set; }
471 + public XElement PackageElement { get; set; }
472 +
473 + public IntermediateSymbol HarvestedPackageSymbol { get; set; }
474
475 public WixBundlePayloadSymbol PayloadSymbol { get; set; }
476
src/wix/test/WixToolsetTest.CoreIntegration/BurnRemotePayloadSubcommandFixture.cs
+78 -1
@@ -148,6 +148,83 @@ namespace WixToolsetTest.CoreIntegration
148 }
149 }
150
151 + [Fact]
152 + public void CanGetRemoteBundlePayloadWithCertificate()
153 + {
154 + var folder = TestData.Get(@"TestData");
155 +
156 + using (var fs = new DisposableFileSystem())
157 + {
158 + var outputFolder = fs.GetFolder();
159 + var outFile = Path.Combine(outputFolder, "out.xml");
160 + var remotePayloadSourceFile = Path.Combine(outputFolder, "remotePayload.wxs");
161 + var intermediateFolder = Path.Combine(outputFolder, "obj");
162 + var bundleFile = Path.Combine(intermediateFolder, "out.exe");
163 + var baFolderPath = Path.Combine(outputFolder, "ba");
164 + var extractFolderPath = Path.Combine(outputFolder, "extract");
165 +
166 + var result = WixRunner.Execute(new[]
167 + {
168 + "burn", "remotepayload",
169 + "-usecertificate",
170 + "-downloadUrl", "http://wixtoolset.org/{0}",
171 + Path.Combine(folder, ".Data", "signed_wix314_4118_engine.exe"),
172 + "-o", outFile,
173 + "-packagetype", "bundle",
174 + });
175 +
176 + result.AssertSuccess();
177 +
178 + var elements = File.ReadAllLines(outFile);
179 + elements = elements.Select(s => s.Replace("\"", "'")).ToArray();
180 +
181 + WixAssert.CompareLineByLine(new[]
182 + {
183 + @"<BundlePackage Visible='yes' CacheId='{C0BA713B-9CFE-42DF-B92C-883F6846B4BA}v3.14.0.4118C95FC39334E667F3DD3D'>",
184 + @" <BundlePackagePayload Name='signed_wix314_4118_engine.exe' ProductName='WiX Toolset v3.14.0.4118' Description='WiX Toolset v3.14.0.4118' DownloadUrl='http://wixtoolset.org/{0}' CertificatePublicKey='03169B5A32E602D436FC14EC14C435D7309945D4' CertificateThumbprint='C95FC39334E667F3DD3D82AF382E05719B88F7C1' Size='1088640' Version='3.14.0.4118'>",
185 + @" <RemoteBundle BundleId='{C0BA713B-9CFE-42DF-B92C-883F6846B4BA}' DisplayName='WiX Toolset v3.14.0.4118' InstallSize='188426175' ManifestNamespace='http://schemas.microsoft.com/wix/2008/Burn' PerMachine='yes' ProviderKey='{c0ba713b-9cfe-42df-b92c-883f6846b4ba}' ProtocolVersion='1' Version='3.14.0.4118' Win64='no' UpgradeCode='{65E893AD-EDD5-4E7D-80CA-F0F50F383532}' />",
186 + @" </BundlePackagePayload>",
187 + @"</BundlePackage>",
188 + }, elements);
189 +
190 + var remotePayloadSourceText = "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>" +
191 + " <Fragment>" +
192 + " <PackageGroup Id='BundlePackages'>" +
193 + String.Join(Environment.NewLine, elements) +
194 + " </PackageGroup>" +
195 + " </Fragment>" +
196 + "</Wix>";
197 +
198 + File.WriteAllText(remotePayloadSourceFile, remotePayloadSourceText);
199 +
200 + result = WixRunner.Execute(new[]
201 + {
202 + "build",
203 + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
204 + remotePayloadSourceFile,
205 + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
206 + "-bindpath", Path.Combine(folder, ".Data"),
207 + "-intermediateFolder", intermediateFolder,
208 + "-o", bundleFile
209 + });
210 +
211 + result.AssertSuccess();
212 +
213 + var extractResult = BundleExtractor.ExtractBAContainer(null, bundleFile, baFolderPath, extractFolderPath);
214 + extractResult.AssertSuccess();
215 +
216 + var msuPackages = extractResult.GetManifestTestXmlLines("/burn:BurnManifest/burn:Chain/burn:BundlePackage");
217 + WixAssert.CompareLineByLine(new string[]
218 + {
219 + "<BundlePackage Id='signed_wix314_4118_engine.exe' Cache='keep' CacheId='{C0BA713B-9CFE-42DF-B92C-883F6846B4BA}v3.14.0.4118C95FC39334E667F3DD3D' InstallSize='188426175' Size='1088640' PerMachine='yes' Permanent='no' Vital='yes' RollbackBoundaryForward='WixDefaultBoundary' RollbackBoundaryBackward='WixDefaultBoundary' LogPathVariable='WixBundleLog_signed_wix314_4118_engine.exe' RollbackLogPathVariable='WixBundleRollbackLog_signed_wix314_4118_engine.exe' BundleId='{C0BA713B-9CFE-42DF-B92C-883F6846B4BA}' Version='3.14.0.4118' InstallArguments='' UninstallArguments='' RepairArguments='' SupportsBurnProtocol='yes' Win64='no'>" +
220 + "<Provides Key='{c0ba713b-9cfe-42df-b92c-883f6846b4ba}' Version='3.14.0.4118' DisplayName='WiX Toolset v3.14.0.4118' Imported='yes' />" +
221 + "<RelatedBundle Id='{65E893AD-EDD5-4E7D-80CA-F0F50F383532}' Action='Upgrade' />" +
222 + "<PayloadRef Id='signed_wix314_4118_engine.exe' />" +
223 + "</BundlePackage>",
224 + }, msuPackages);
225 + }
226 + }
227 +
228 [Fact]
229 public void CanGetRemoteV3BundlePayload()
230 {
@@ -174,7 +251,7 @@ namespace WixToolsetTest.CoreIntegration
251
252 WixAssert.CompareLineByLine(new[]
253 {
177 - "<BundlePackage>",
254 + "<BundlePackage Visible='yes'>",
255 " <BundlePackagePayload Name='v3bundle.exe' ProductName='CustomV3Theme' Description='CustomV3Theme' DownloadUrl='https://www.example.com/files/{0}' Hash='80739E7B8C31D75B4CDC48D60D74F5E481CB904212A3AE3FB0920365A163FBF32B0C5C175AB516D4124F107923E96200605DE1D560D362FEB47350FA727823B4' Size='648397' Version='1.0.0.0'>",
256 " <RemoteBundle BundleId='{215A70DB-AB35-48C7-BE51-D66EAAC87177}' DisplayName='CustomV3Theme' InstallSize='1135' ManifestNamespace='http://schemas.microsoft.com/wix/2008/Burn' PerMachine='yes' ProviderKey='{215a70db-ab35-48c7-be51-d66eaac87177}' ProtocolVersion='1' Version='1.0.0.0' Win64='no' UpgradeCode='{2BF4C01F-C132-4E70-97AB-2BC68C7CCD10}' />",
257 " </BundlePackagePayload>",
src/wix/test/WixToolsetTest.CoreIntegration/TestData/.Data/signed_wix314_4118_engine.exe
Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/.Data/signed_wix314_4118_engine.exe differ