aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2022-02-25 22:24:42 -0800
committerRob Mensching <rob@firegiant.com>2022-02-28 16:56:09 -0800
commit5fd804165587b3b6d2bd6b9844dcb3fa55a4a305 (patch)
tree2fe3f82b049734845e8a0f1a0719aabaa8bd3d19
parent82bfd1218699a08c8d9cd775fc9e3eef3ec519a2 (diff)
downloadwix-5fd804165587b3b6d2bd6b9844dcb3fa55a4a305.tar.gz
wix-5fd804165587b3b6d2bd6b9844dcb3fa55a4a305.tar.bz2
wix-5fd804165587b3b6d2bd6b9844dcb3fa55a4a305.zip
Support certs on remote payloads and generate them from burn subcommand
Bring back Authenticode certificate validation but only on Exe and Msu remote payloads. Move the generation of remote payload XML to a subcommand of the "burn command".
-rw-r--r--src/api/wix/WixToolset.Data/Symbols/WixBundlePayloadSymbol.cs16
-rw-r--r--src/wix/WixToolset.Converters.Symbolizer/WixToolset.Converters.Symbolizer.v3.ncrunchproject7
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs11
-rw-r--r--src/wix/WixToolset.Core.Burn/BurnBackendErrors.cs6
-rw-r--r--src/wix/WixToolset.Core.Burn/CommandLine/BurnCommand.cs4
-rw-r--r--src/wix/WixToolset.Core.Burn/CommandLine/RemotePayloadSubcommand.cs272
-rw-r--r--src/wix/WixToolset.Core.Native/CertificateHashes.cs83
-rw-r--r--src/wix/WixToolset.Core.Native/WixNativeExe.cs2
-rw-r--r--src/wix/WixToolset.Core/Compile/CompilerPayload.cs89
-rw-r--r--src/wix/WixToolset.Core/Compiler_Bundle.cs14
-rw-r--r--src/wix/heat/PayloadHarvester.cs129
-rw-r--r--src/wix/heat/UtilHeatExtension.cs8
-rw-r--r--src/wix/test/WixToolsetTest.Converters.Symbolizer/WixToolsetTest.Converters.Symbolizer.v3.ncrunchproject3
-rw-r--r--src/wix/test/WixToolsetTest.Core.Native/CertificateHashesFixture.cs58
-rw-r--r--src/wix/test/WixToolsetTest.Core.Native/TestData/test.cabbin115 -> 8027 bytes
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs122
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/RemotePayloadFixture.cs172
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/.Data/Windows8.1-KB2937592-x86.msu (renamed from src/wix/test/WixToolsetTest.Heat/TestData/.Data/Windows8.1-KB2937592-x86.msu)bin309544 -> 309544 bytes
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedCertificate.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedCertificatePublicKeyWithoutThumbprint.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndCertificatePublicKey.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndCertificatePublicKey.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndCertificateThumbprint.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/a.dat1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/subfolder/b.dat1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/subfolder/c.dat1
-rw-r--r--src/wix/test/WixToolsetTest.Heat/PayloadTests.cs66
-rw-r--r--src/wix/test/WixToolsetTest.Heat/TestData/.Data/burn.exebin463360 -> 0 bytes
-rw-r--r--src/wix/test/WixToolsetTest.Heat/TestData/Payload/HarvestedExePackagePayload.wxs6
-rw-r--r--src/wix/test/WixToolsetTest.Heat/TestData/Payload/HarvestedMsuPackagePayload.wxs6
-rw-r--r--src/wix/test/WixToolsetTest.Heat/WixToolsetTest.Heat.csproj4
-rw-r--r--src/wix/wixnative/certhashes.cpp138
-rw-r--r--src/wix/wixnative/precomp.h4
-rw-r--r--src/wix/wixnative/wixnative.cpp4
-rw-r--r--src/wix/wixnative/wixnative.vcxproj3
35 files changed, 1033 insertions, 247 deletions
diff --git a/src/api/wix/WixToolset.Data/Symbols/WixBundlePayloadSymbol.cs b/src/api/wix/WixToolset.Data/Symbols/WixBundlePayloadSymbol.cs
index 82b75285..be581fb3 100644
--- a/src/api/wix/WixToolset.Data/Symbols/WixBundlePayloadSymbol.cs
+++ b/src/api/wix/WixToolset.Data/Symbols/WixBundlePayloadSymbol.cs
@@ -26,6 +26,8 @@ namespace WixToolset.Data
26 new IntermediateFieldDefinition(nameof(WixBundlePayloadSymbolFields.LayoutOnly), IntermediateFieldType.Bool), 26 new IntermediateFieldDefinition(nameof(WixBundlePayloadSymbolFields.LayoutOnly), IntermediateFieldType.Bool),
27 new IntermediateFieldDefinition(nameof(WixBundlePayloadSymbolFields.Packaging), IntermediateFieldType.Number), 27 new IntermediateFieldDefinition(nameof(WixBundlePayloadSymbolFields.Packaging), IntermediateFieldType.Number),
28 new IntermediateFieldDefinition(nameof(WixBundlePayloadSymbolFields.ParentPackagePayloadRef), IntermediateFieldType.String), 28 new IntermediateFieldDefinition(nameof(WixBundlePayloadSymbolFields.ParentPackagePayloadRef), IntermediateFieldType.String),
29 new IntermediateFieldDefinition(nameof(WixBundlePayloadSymbolFields.CertificatePublicKey), IntermediateFieldType.String),
30 new IntermediateFieldDefinition(nameof(WixBundlePayloadSymbolFields.CertificateThumbprint), IntermediateFieldType.String),
29 }, 31 },
30 typeof(WixBundlePayloadSymbol)); 32 typeof(WixBundlePayloadSymbol));
31 } 33 }
@@ -53,6 +55,8 @@ namespace WixToolset.Data.Symbols
53 LayoutOnly, 55 LayoutOnly,
54 Packaging, 56 Packaging,
55 ParentPackagePayloadRef, 57 ParentPackagePayloadRef,
58 CertificatePublicKey,
59 CertificateThumbprint,
56 } 60 }
57 61
58 public class WixBundlePayloadSymbol : IntermediateSymbol 62 public class WixBundlePayloadSymbol : IntermediateSymbol
@@ -162,5 +166,17 @@ namespace WixToolset.Data.Symbols
162 get => (string)this.Fields[(int)WixBundlePayloadSymbolFields.ParentPackagePayloadRef]; 166 get => (string)this.Fields[(int)WixBundlePayloadSymbolFields.ParentPackagePayloadRef];
163 set => this.Set((int)WixBundlePayloadSymbolFields.ParentPackagePayloadRef, value); 167 set => this.Set((int)WixBundlePayloadSymbolFields.ParentPackagePayloadRef, value);
164 } 168 }
169
170 public string CertificatePublicKey
171 {
172 get => (string)this.Fields[(int)WixBundlePayloadSymbolFields.CertificatePublicKey];
173 set => this.Set((int)WixBundlePayloadSymbolFields.CertificatePublicKey, value);
174 }
175
176 public string CertificateThumbprint
177 {
178 get => (string)this.Fields[(int)WixBundlePayloadSymbolFields.CertificateThumbprint];
179 set => this.Set((int)WixBundlePayloadSymbolFields.CertificateThumbprint, value);
180 }
165 } 181 }
166} 182}
diff --git a/src/wix/WixToolset.Converters.Symbolizer/WixToolset.Converters.Symbolizer.v3.ncrunchproject b/src/wix/WixToolset.Converters.Symbolizer/WixToolset.Converters.Symbolizer.v3.ncrunchproject
new file mode 100644
index 00000000..cd54b69b
--- /dev/null
+++ b/src/wix/WixToolset.Converters.Symbolizer/WixToolset.Converters.Symbolizer.v3.ncrunchproject
@@ -0,0 +1,7 @@
1<ProjectConfiguration>
2 <Settings>
3 <HiddenComponentWarnings>
4 <Value>LostReference</Value>
5 </HiddenComponentWarnings>
6 </Settings>
7</ProjectConfiguration> \ No newline at end of file
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs
index c8f10a71..af45e736 100644
--- a/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs
+++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs
@@ -672,7 +672,16 @@ namespace WixToolset.Core.Burn.Bundles
672 writer.WriteAttributeString("Id", payload.Id.Id); 672 writer.WriteAttributeString("Id", payload.Id.Id);
673 writer.WriteAttributeString("FilePath", payload.Name); 673 writer.WriteAttributeString("FilePath", payload.Name);
674 writer.WriteAttributeString("FileSize", payload.FileSize.Value.ToString(CultureInfo.InvariantCulture)); 674 writer.WriteAttributeString("FileSize", payload.FileSize.Value.ToString(CultureInfo.InvariantCulture));
675 writer.WriteAttributeString("Hash", payload.Hash); 675
676 if (!String.IsNullOrEmpty(payload.CertificatePublicKey) && !String.IsNullOrEmpty(payload.CertificateThumbprint))
677 {
678 writer.WriteAttributeString("CertificateRootPublicKeyIdentifier", payload.CertificatePublicKey);
679 writer.WriteAttributeString("CertificateRootThumbprint", payload.CertificateThumbprint);
680 }
681 else
682 {
683 writer.WriteAttributeString("Hash", payload.Hash);
684 }
676 685
677 if (payload.LayoutOnly) 686 if (payload.LayoutOnly)
678 { 687 {
diff --git a/src/wix/WixToolset.Core.Burn/BurnBackendErrors.cs b/src/wix/WixToolset.Core.Burn/BurnBackendErrors.cs
index fd6eb093..74dc4cd5 100644
--- a/src/wix/WixToolset.Core.Burn/BurnBackendErrors.cs
+++ b/src/wix/WixToolset.Core.Burn/BurnBackendErrors.cs
@@ -56,6 +56,11 @@ namespace WixToolset.Core.Burn
56 return Message(null, Ids.IncompatibleWixBurnSection, "Unable to read bundle executable '{0}', because this bundle was created with a different version of WiX burn (.wixburn section version '{1}'). You must use the same version of Windows Installer XML in order to read this bundle.", bundleExecutable, bundleVersion); 56 return Message(null, Ids.IncompatibleWixBurnSection, "Unable to read bundle executable '{0}', because this bundle was created with a different version of WiX burn (.wixburn section version '{1}'). You must use the same version of Windows Installer XML in order to read this bundle.", bundleExecutable, bundleVersion);
57 } 57 }
58 58
59 public static Message UnsupportedRemotePackagePayload(string extension, string path)
60 {
61 return Message(null, Ids.UnsupportedRemotePackagePayload, "The first remote payload must be a supported package type of .exe or .msu. Use the -packageType switch to override the inferred extension: {0} from file: {1}", extension, path);
62 }
63
59 private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) 64 private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args)
60 { 65 {
61 return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); 66 return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args);
@@ -73,6 +78,7 @@ namespace WixToolset.Core.Burn
73 PackageCachePayloadCollision2 = 8007, 78 PackageCachePayloadCollision2 = 8007,
74 TooManyAttachedContainers = 8008, 79 TooManyAttachedContainers = 8008,
75 IncompatibleWixBurnSection = 8009, 80 IncompatibleWixBurnSection = 8009,
81 UnsupportedRemotePackagePayload = 8010,
76 } // last available is 8499. 8500 is BurnBackendWarnings. 82 } // last available is 8499. 8500 is BurnBackendWarnings.
77 } 83 }
78} 84}
diff --git a/src/wix/WixToolset.Core.Burn/CommandLine/BurnCommand.cs b/src/wix/WixToolset.Core.Burn/CommandLine/BurnCommand.cs
index a50fb7cd..b552678f 100644
--- a/src/wix/WixToolset.Core.Burn/CommandLine/BurnCommand.cs
+++ b/src/wix/WixToolset.Core.Burn/CommandLine/BurnCommand.cs
@@ -52,6 +52,10 @@ namespace WixToolset.Core.Burn.CommandLine
52 case "reattach": 52 case "reattach":
53 this.Subcommand = new ReattachSubcommand(this.ServiceProvider); 53 this.Subcommand = new ReattachSubcommand(this.ServiceProvider);
54 return true; 54 return true;
55
56 case "remotepayload":
57 this.Subcommand = new RemotePayloadSubcommand(this.ServiceProvider);
58 return true;
55 } 59 }
56 60
57 return false; 61 return false;
diff --git a/src/wix/WixToolset.Core.Burn/CommandLine/RemotePayloadSubcommand.cs b/src/wix/WixToolset.Core.Burn/CommandLine/RemotePayloadSubcommand.cs
new file mode 100644
index 00000000..7b362485
--- /dev/null
+++ b/src/wix/WixToolset.Core.Burn/CommandLine/RemotePayloadSubcommand.cs
@@ -0,0 +1,272 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.CommandLine
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using System.Threading;
10 using System.Threading.Tasks;
11 using System.Xml.Linq;
12 using WixToolset.Core.Burn.Interfaces;
13 using WixToolset.Core.Native;
14 using WixToolset.Data;
15 using WixToolset.Data.Symbols;
16 using WixToolset.Extensibility.Services;
17
18 internal class RemotePayloadSubcommand : BurnSubcommandBase
19 {
20 private static readonly XName ExePackagePayloadName = "ExePackagePayload";
21 private static readonly XName MsuPackagePayloadName = "MsuPackagePayload";
22 private static readonly XName PayloadName = "Payload";
23
24 public RemotePayloadSubcommand(IServiceProvider serviceProvider)
25 {
26 this.Messaging = serviceProvider.GetService<IMessaging>();
27 this.PayloadHarvester = serviceProvider.GetService<IPayloadHarvester>();
28 }
29
30 private IMessaging Messaging { get; }
31
32 private IPayloadHarvester PayloadHarvester { get; }
33
34 private List<string> BasePaths { get; } = new List<string>();
35
36 private string DownloadUrl { get; set; }
37
38 private List<string> InputPaths { get; } = new List<string>();
39
40 private string OutputPath { get; set; }
41
42 private WixBundlePackageType? PackageType { get; set; }
43
44 private bool Recurse { get; set; }
45
46 private bool UseCertificate { get; set; }
47
48 public override Task<int> ExecuteAsync(CancellationToken cancellationToken)
49 {
50 var inputPaths = this.ExpandInputPaths();
51 if (inputPaths.Count == 0)
52 {
53 Console.Error.WriteLine("Path to a remote payload is required");
54 return Task.FromResult(-1);
55 }
56
57 // Reverse sort to ensure longest paths are matched first.
58 this.BasePaths.Sort();
59 this.BasePaths.Reverse();
60
61 var elements = this.HarvestRemotePayloads(inputPaths);
62
63 if (!this.Messaging.EncounteredError)
64 {
65 if (!String.IsNullOrEmpty(this.OutputPath))
66 {
67 var outputFolder = Path.GetDirectoryName(this.OutputPath);
68 Directory.CreateDirectory(outputFolder);
69
70 File.WriteAllLines(this.OutputPath, elements.Select(e => e.ToString()));
71 }
72 else
73 {
74 foreach (var element in elements)
75 {
76 Console.WriteLine(element);
77 }
78 }
79 }
80
81 return Task.FromResult(this.Messaging.LastErrorNumber);
82 }
83
84 public override bool TryParseArgument(ICommandLineParser parser, string argument)
85 {
86 if (parser.IsSwitch(argument))
87 {
88 var parameter = argument.Substring(1);
89 switch (parameter.ToLowerInvariant())
90 {
91 case "bp":
92 case "basepath":
93 this.BasePaths.Add(parser.GetNextArgumentAsDirectoryOrError(argument));
94 return true;
95
96 case "du":
97 case "downloadurl":
98 this.DownloadUrl = parser.GetNextArgumentOrError(argument);
99 return true;
100
101 case "packagetype":
102 var packageTypeValue = parser.GetNextArgumentOrError(argument);
103 if (Enum.TryParse<WixBundlePackageType>(packageTypeValue, ignoreCase: true, out var packageType))
104 {
105 this.PackageType = packageType;
106 return true;
107 }
108 break;
109
110 case "o":
111 case "out":
112 this.OutputPath = parser.GetNextArgumentAsFilePathOrError(argument);
113 return true;
114
115 case "r":
116 case "recurse":
117 this.Recurse = true;
118 return true;
119
120 case "usecertificate":
121 this.UseCertificate = true;
122 return true;
123 }
124 }
125 else
126 {
127 this.InputPaths.Add(argument);
128 return true;
129 }
130
131 return false;
132 }
133
134 private IReadOnlyCollection<string> ExpandInputPaths()
135 {
136 var result = new List<string>();
137
138 foreach (var inputPath in this.InputPaths)
139 {
140 var filename = Path.GetFileName(inputPath);
141 var folder = Path.GetDirectoryName(inputPath);
142
143 if (String.IsNullOrEmpty(folder))
144 {
145 folder = ".";
146 }
147
148 foreach (var path in Directory.EnumerateFiles(folder, filename, this.Recurse ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly))
149 {
150 result.Add(path);
151 }
152 }
153
154 return result.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
155 }
156
157 private IEnumerable<XElement> HarvestRemotePayloads(IEnumerable<string> paths)
158 {
159 var first = true;
160 var hashes = new Dictionary<string, CertificateHashes>();
161
162 if (this.UseCertificate)
163 {
164 hashes = CertificateHashes.Read(paths).Where(c => !String.IsNullOrEmpty(c.PublicKey) && !String.IsNullOrEmpty(c.Thumbprint) && c.Exception is null).ToDictionary(c => c.Path);
165 }
166
167 foreach (var path in paths)
168 {
169 var element = this.CreateRemotePayloadElement(path, first);
170 first = false;
171
172 if (element == null)
173 {
174 continue;
175 }
176
177 var payloadSymbol = new WixBundlePayloadSymbol
178 {
179 SourceFile = new IntermediateFieldPathValue { Path = path },
180 };
181
182 this.PayloadHarvester.HarvestStandardInformation(payloadSymbol);
183
184 element.Add(new XAttribute("Name", Path.GetFileName(path)));
185
186 if (!String.IsNullOrEmpty(payloadSymbol.DisplayName))
187 {
188 element.Add(new XAttribute("ProductName", payloadSymbol.DisplayName));
189 }
190
191 if (!String.IsNullOrEmpty(payloadSymbol.Description))
192 {
193 element.Add(new XAttribute("Description", payloadSymbol.Description));
194 }
195
196 if (!String.IsNullOrEmpty(this.DownloadUrl))
197 {
198 var filename = this.GetRelativeFileName(payloadSymbol.SourceFile.Path);
199 var formattedUrl = String.Format(this.DownloadUrl, filename);
200
201 if (Uri.TryCreate(formattedUrl, UriKind.Absolute, out var url))
202 {
203 element.Add(new XAttribute("DownloadUrl", url.AbsoluteUri));
204 }
205 }
206
207 if (hashes.TryGetValue(path, out var certificateHashes))
208 {
209 element.Add(new XAttribute("CertificatePublicKey", certificateHashes.PublicKey));
210 element.Add(new XAttribute("CertificateThumbprint", certificateHashes.Thumbprint));
211 }
212
213 if (!String.IsNullOrEmpty(payloadSymbol.Hash))
214 {
215 element.Add(new XAttribute("Hash", payloadSymbol.Hash));
216 }
217
218 if (payloadSymbol.FileSize.HasValue)
219 {
220 element.Add(new XAttribute("Size", payloadSymbol.FileSize.Value));
221 }
222
223 if (!String.IsNullOrEmpty(payloadSymbol.Version))
224 {
225 element.Add(new XAttribute("Version", payloadSymbol.Version));
226 }
227
228 yield return element;
229 }
230 }
231
232 private XElement CreateRemotePayloadElement(string path, bool firstHarvest)
233 {
234 if (firstHarvest)
235 {
236 var extension = this.PackageType.HasValue ? this.PackageType.ToString() : Path.GetExtension(path);
237
238 switch (extension.ToUpperInvariant())
239 {
240 case "EXE":
241 case ".EXE":
242 return new XElement(ExePackagePayloadName);
243
244 case "MSU":
245 case ".MSU":
246 return new XElement(MsuPackagePayloadName);
247
248 default:
249 this.Messaging.Write(BurnBackendErrors.UnsupportedRemotePackagePayload(extension, path));
250 return null;
251 }
252 }
253 else
254 {
255 return new XElement(PayloadName);
256 }
257 }
258
259 private string GetRelativeFileName(string path)
260 {
261 foreach (var basePath in this.BasePaths)
262 {
263 if (path.StartsWith(basePath, StringComparison.OrdinalIgnoreCase))
264 {
265 return path.Substring(basePath.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
266 }
267 }
268
269 return Path.GetFileName(path);
270 }
271 }
272}
diff --git a/src/wix/WixToolset.Core.Native/CertificateHashes.cs b/src/wix/WixToolset.Core.Native/CertificateHashes.cs
new file mode 100644
index 00000000..a024c923
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/CertificateHashes.cs
@@ -0,0 +1,83 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native
4{
5 using System;
6 using System.Collections.Generic;
7 using System.ComponentModel;
8 using System.Globalization;
9 using System.Linq;
10
11 /// <summary>
12 /// Read certificates' public key and thumbprint hashes.
13 /// </summary>
14 public sealed class CertificateHashes
15 {
16 private static readonly char[] TextLineSplitter = new[] { '\t' };
17
18 private CertificateHashes(string path, string publicKey, string thumbprint, Exception exception)
19 {
20 this.Path = path;
21 this.PublicKey = publicKey;
22 this.Thumbprint = thumbprint;
23 this.Exception = exception;
24 }
25
26 /// <summary>
27 /// Path to the file read.
28 /// </summary>
29 public string Path { get; }
30
31 /// <summary>
32 /// Hash of the certificate's public key.
33 /// </summary>
34 public string PublicKey { get; }
35
36 /// <summary>
37 /// Hash of the certificate's thumbprint.
38 /// </summary>
39 public string Thumbprint { get; }
40
41 /// <summary>
42 /// Exception encountered while trying to read certificate's hash.
43 /// </summary>
44 public Exception Exception { get; }
45
46 /// <summary>
47 /// Read the certificate hashes from the provided paths.
48 /// </summary>
49 /// <param name="paths">Paths to read for certificates.</param>
50 /// <returns>Certificate hashes for the provided paths.</returns>
51 public static IReadOnlyList<CertificateHashes> Read(IEnumerable<string> paths)
52 {
53 var result = new List<CertificateHashes>();
54
55 var wixnative = new WixNativeExe("certhashes");
56
57 foreach (var path in paths)
58 {
59 wixnative.AddStdinLine(path);
60 }
61
62 try
63 {
64 var outputLines = wixnative.Run();
65 foreach (var line in outputLines.Where(l => !String.IsNullOrEmpty(l)))
66 {
67 var data = line.Split(TextLineSplitter, StringSplitOptions.None);
68
69 var error = Int32.Parse(data[3].Substring(2), NumberStyles.HexNumber);
70 var exception = error != 0 ? new Win32Exception(error) : null;
71
72 result.Add(new CertificateHashes(data[0], data[1], data[2], exception));
73 }
74 }
75 catch (Exception e)
76 {
77 result.Add(new CertificateHashes(null, null, null, e));
78 }
79
80 return result;
81 }
82 }
83}
diff --git a/src/wix/WixToolset.Core.Native/WixNativeExe.cs b/src/wix/WixToolset.Core.Native/WixNativeExe.cs
index 13d7a4ea..f5ff8bf0 100644
--- a/src/wix/WixToolset.Core.Native/WixNativeExe.cs
+++ b/src/wix/WixToolset.Core.Native/WixNativeExe.cs
@@ -30,7 +30,7 @@ namespace WixToolset.Core.Native
30 this.stdinLines.AddRange(lines); 30 this.stdinLines.AddRange(lines);
31 } 31 }
32 32
33 public IEnumerable<string> Run() 33 public IReadOnlyCollection<string> Run()
34 { 34 {
35 EnsurePathToWixNativeExeSet(); 35 EnsurePathToWixNativeExeSet();
36 36
diff --git a/src/wix/WixToolset.Core/Compile/CompilerPayload.cs b/src/wix/WixToolset.Core/Compile/CompilerPayload.cs
index ea63ee8c..cee9b377 100644
--- a/src/wix/WixToolset.Core/Compile/CompilerPayload.cs
+++ b/src/wix/WixToolset.Core/Compile/CompilerPayload.cs
@@ -11,12 +11,31 @@ namespace WixToolset.Core
11 11
12 internal class CompilerPayload 12 internal class CompilerPayload
13 { 13 {
14 public string Version { get; set; }
15
16 public CompilerPayload(CompilerCore core, SourceLineNumber sourceLineNumbers, XElement element)
17 {
18 this.Core = core;
19 this.Element = element;
20 this.SourceLineNumbers = sourceLineNumbers;
21 }
22
23 private CompilerCore Core { get; }
24
25 private XElement Element { get; }
26
27 private SourceLineNumber SourceLineNumbers { get; }
28
14 public YesNoDefaultType Compressed { get; set; } = YesNoDefaultType.Default; 29 public YesNoDefaultType Compressed { get; set; } = YesNoDefaultType.Default;
15 30
16 public string Description { get; set; } 31 public string Description { get; set; }
17 32
18 public string DownloadUrl { get; set; } 33 public string DownloadUrl { get; set; }
19 34
35 public string CertificatePublicKey { get; set; }
36
37 public string CertificateThumbprint { get; set; }
38
20 public string Hash { get; set; } 39 public string Hash { get; set; }
21 40
22 public Identifier Id { get; set; } 41 public Identifier Id { get; set; }
@@ -33,24 +52,9 @@ namespace WixToolset.Core
33 52
34 public string SourceFile { get; set; } 53 public string SourceFile { get; set; }
35 54
36 public string Version { get; set; }
37
38 public CompilerPayload(CompilerCore core, SourceLineNumber sourceLineNumbers, XElement element)
39 {
40 this.Core = core;
41 this.Element = element;
42 this.SourceLineNumbers = sourceLineNumbers;
43 }
44
45 private CompilerCore Core { get; }
46
47 private XElement Element { get; }
48
49 private SourceLineNumber SourceLineNumbers { get; }
50
51 private void CalculateAndVerifyFields() 55 private void CalculateAndVerifyFields()
52 { 56 {
53 var isRemote = this.IsRemoteAllowed && !String.IsNullOrEmpty(this.Hash); 57 var isRemote = this.IsRemoteAllowed && (!String.IsNullOrEmpty(this.CertificatePublicKey) || !String.IsNullOrEmpty(this.CertificateThumbprint) || !String.IsNullOrEmpty(this.Hash));
54 58
55 if (String.IsNullOrEmpty(this.SourceFile)) 59 if (String.IsNullOrEmpty(this.SourceFile))
56 { 60 {
@@ -81,7 +85,7 @@ namespace WixToolset.Core
81 } 85 }
82 else 86 else
83 { 87 {
84 this.Core.Write(ErrorMessages.ExpectedAttributes(this.SourceLineNumbers, this.Element.Name.LocalName, "SourceFile", "Hash")); 88 this.Core.Write(ErrorMessages.ExpectedAttributes(this.SourceLineNumbers, this.Element.Name.LocalName, "SourceFile", "CertificatePublicKey", "Hash"));
85 } 89 }
86 } 90 }
87 } 91 }
@@ -93,7 +97,20 @@ namespace WixToolset.Core
93 { 97 {
94 if (isRemote) 98 if (isRemote)
95 { 99 {
96 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Hash", "SourceFile")); 100 if (!String.IsNullOrEmpty(this.Hash))
101 {
102 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Hash", "SourceFile"));
103 }
104
105 if (!String.IsNullOrEmpty(this.CertificatePublicKey))
106 {
107 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "CertificatePublicKey", "SourceFile"));
108 }
109
110 if (!String.IsNullOrEmpty(this.CertificateThumbprint))
111 {
112 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "CertificateThumbprint", "SourceFile"));
113 }
97 } 114 }
98 115
99 if (!String.IsNullOrEmpty(this.Description)) 116 if (!String.IsNullOrEmpty(this.Description))
@@ -120,17 +137,34 @@ namespace WixToolset.Core
120 { 137 {
121 if (String.IsNullOrEmpty(this.DownloadUrl)) 138 if (String.IsNullOrEmpty(this.DownloadUrl))
122 { 139 {
123 this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "DownloadUrl", "Hash")); 140 this.Core.Write(ErrorMessages.ExpectedAttributeWithoutOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "DownloadUrl", "SourceFile"));
124 } 141 }
125 142
126 if (String.IsNullOrEmpty(this.Name)) 143 if (String.IsNullOrEmpty(this.Name))
127 { 144 {
128 this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "Hash")); 145 this.Core.Write(ErrorMessages.ExpectedAttributeWithoutOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "SourceFile"));
129 } 146 }
130 147
131 if (!this.Size.HasValue) 148 if (!this.Size.HasValue)
132 { 149 {
133 this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Size", "Hash")); 150 this.Core.Write(ErrorMessages.ExpectedAttributeWithoutOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Size", "SourceFile"));
151 }
152
153 if (String.IsNullOrEmpty(this.Hash))
154 {
155 this.Core.Write(ErrorMessages.ExpectedAttributeWithoutOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Hash", "SourceFile"));
156 }
157
158 if (!String.IsNullOrEmpty(this.CertificatePublicKey) || !String.IsNullOrEmpty(this.CertificateThumbprint))
159 {
160 if (String.IsNullOrEmpty(this.CertificateThumbprint))
161 {
162 this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "CertificateThumbprint", "CertificatePublicKey"));
163 }
164 else if (String.IsNullOrEmpty(this.CertificatePublicKey))
165 {
166 this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "CertificatePublicKey", "CertificateThumbprint"));
167 }
134 } 168 }
135 169
136 if (YesNoDefaultType.Yes == this.Compressed) 170 if (YesNoDefaultType.Yes == this.Compressed)
@@ -177,6 +211,8 @@ namespace WixToolset.Core
177 Hash = this.Hash, 211 Hash = this.Hash,
178 FileSize = this.Size, 212 FileSize = this.Size,
179 Version = this.Version, 213 Version = this.Version,
214 CertificatePublicKey = this.CertificatePublicKey,
215 CertificateThumbprint = this.CertificateThumbprint
180 }); 216 });
181 217
182 this.Core.CreateGroupAndOrderingRows(this.SourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Payload, symbol.Id.Id, ComplexReferenceChildType.Unknown, null); 218 this.Core.CreateGroupAndOrderingRows(this.SourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Payload, symbol.Id.Id, ComplexReferenceChildType.Unknown, null);
@@ -248,6 +284,16 @@ namespace WixToolset.Core
248 this.DownloadUrl = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); 284 this.DownloadUrl = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib);
249 } 285 }
250 286
287 public void ParseCertificatePublicKey(XAttribute attrib)
288 {
289 this.CertificatePublicKey = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib);
290 }
291
292 public void ParseCertificateThumbprint(XAttribute attrib)
293 {
294 this.CertificateThumbprint = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib);
295 }
296
251 public void ParseHash(XAttribute attrib) 297 public void ParseHash(XAttribute attrib)
252 { 298 {
253 this.Hash = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); 299 this.Hash = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib);
@@ -286,6 +332,5 @@ namespace WixToolset.Core
286 { 332 {
287 this.Version = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); 333 this.Version = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib);
288 } 334 }
289
290 } 335 }
291} 336}
diff --git a/src/wix/WixToolset.Core/Compiler_Bundle.cs b/src/wix/WixToolset.Core/Compiler_Bundle.cs
index c8b78243..3fde990e 100644
--- a/src/wix/WixToolset.Core/Compiler_Bundle.cs
+++ b/src/wix/WixToolset.Core/Compiler_Bundle.cs
@@ -2459,6 +2459,20 @@ namespace WixToolset.Core
2459 case "SourceFile": 2459 case "SourceFile":
2460 compilerPayload.ParseSourceFile(attrib); 2460 compilerPayload.ParseSourceFile(attrib);
2461 break; 2461 break;
2462 case "CertificatePublicKey":
2463 allowed = compilerPayload.IsRemoteAllowed;
2464 if (allowed)
2465 {
2466 compilerPayload.ParseCertificatePublicKey(attrib);
2467 }
2468 break;
2469 case "CertificateThumbprint":
2470 allowed = compilerPayload.IsRemoteAllowed;
2471 if (allowed)
2472 {
2473 compilerPayload.ParseCertificateThumbprint(attrib);
2474 }
2475 break;
2462 case "DownloadUrl": 2476 case "DownloadUrl":
2463 compilerPayload.ParseDownloadUrl(attrib); 2477 compilerPayload.ParseDownloadUrl(attrib);
2464 break; 2478 break;
diff --git a/src/wix/heat/PayloadHarvester.cs b/src/wix/heat/PayloadHarvester.cs
deleted file mode 100644
index d2492512..00000000
--- a/src/wix/heat/PayloadHarvester.cs
+++ /dev/null
@@ -1,129 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Harvesters
4{
5 using System;
6 using System.IO;
7 using WixToolset.Core.Burn.Interfaces;
8 using WixToolset.Data;
9 using WixToolset.Data.Symbols;
10 using WixToolset.Harvesters.Data;
11 using WixToolset.Harvesters.Extensibility;
12 using Wix = WixToolset.Harvesters.Serialize;
13
14 /// <summary>
15 /// Harvest WiX authoring for a payload from the file system.
16 /// </summary>
17 public sealed class PayloadHarvester : BaseHarvesterExtension
18 {
19 private bool setUniqueIdentifiers;
20 private WixBundlePackageType packageType;
21
22 private IPayloadHarvester payloadHarvester;
23
24 /// <summary>
25 /// Instantiate a new PayloadHarvester.
26 /// </summary>
27 public PayloadHarvester(IPayloadHarvester payloadHarvester, WixBundlePackageType packageType)
28 {
29 this.payloadHarvester = payloadHarvester;
30
31 this.packageType = packageType;
32 this.setUniqueIdentifiers = true;
33 }
34
35 /// <summary>
36 /// Gets of sets the option to set unique identifiers.
37 /// </summary>
38 /// <value>The option to set unique identifiers.</value>
39 public bool SetUniqueIdentifiers
40 {
41 get { return this.setUniqueIdentifiers; }
42 set { this.setUniqueIdentifiers = value; }
43 }
44
45 /// <summary>
46 /// Harvest a payload.
47 /// </summary>
48 /// <param name="argument">The path of the payload.</param>
49 /// <returns>A harvested payload.</returns>
50 public override Wix.Fragment[] Harvest(string argument)
51 {
52 if (null == argument)
53 {
54 throw new ArgumentNullException("argument");
55 }
56
57 string fullPath = Path.GetFullPath(argument);
58
59 var remotePayload = this.HarvestRemotePayload(fullPath);
60
61 var fragment = new Wix.Fragment();
62 fragment.AddChild(remotePayload);
63
64 return new Wix.Fragment[] { fragment };
65 }
66
67 /// <summary>
68 /// Harvest a payload.
69 /// </summary>
70 /// <param name="path">The path of the payload.</param>
71 /// <returns>A harvested payload.</returns>
72 public Wix.RemotePayload HarvestRemotePayload(string path)
73 {
74 if (null == path)
75 {
76 throw new ArgumentNullException("path");
77 }
78
79 if (!File.Exists(path))
80 {
81 throw new WixException(HarvesterErrors.FileNotFound(path));
82 }
83
84 Wix.RemotePayload remotePayload;
85
86 switch (this.packageType)
87 {
88 case WixBundlePackageType.Exe:
89 remotePayload = new Wix.ExePackagePayload();
90 break;
91 case WixBundlePackageType.Msu:
92 remotePayload = new Wix.MsuPackagePayload();
93 break;
94 default:
95 throw new NotImplementedException();
96 }
97
98 var payloadSymbol = new WixBundlePayloadSymbol
99 {
100 SourceFile = new IntermediateFieldPathValue { Path = path },
101 };
102
103 this.payloadHarvester.HarvestStandardInformation(payloadSymbol);
104
105 if (payloadSymbol.FileSize.HasValue)
106 {
107 remotePayload.Size = payloadSymbol.FileSize.Value;
108 }
109 remotePayload.Hash = payloadSymbol.Hash;
110
111 if (!String.IsNullOrEmpty(payloadSymbol.Version))
112 {
113 remotePayload.Version = payloadSymbol.Version;
114 }
115
116 if (!String.IsNullOrEmpty(payloadSymbol.Description))
117 {
118 remotePayload.Description = payloadSymbol.Description;
119 }
120
121 if (!String.IsNullOrEmpty(payloadSymbol.DisplayName))
122 {
123 remotePayload.ProductName = payloadSymbol.DisplayName;
124 }
125
126 return remotePayload;
127 }
128 }
129}
diff --git a/src/wix/heat/UtilHeatExtension.cs b/src/wix/heat/UtilHeatExtension.cs
index e5be9cac..5ad5ef8a 100644
--- a/src/wix/heat/UtilHeatExtension.cs
+++ b/src/wix/heat/UtilHeatExtension.cs
@@ -87,14 +87,6 @@ namespace WixToolset.Harvesters
87 harvesterExtension = new FileHarvester(); 87 harvesterExtension = new FileHarvester();
88 active = true; 88 active = true;
89 break; 89 break;
90 case "exepackagepayload":
91 harvesterExtension = new PayloadHarvester(this.PayloadHarvester, WixBundlePackageType.Exe);
92 active = true;
93 break;
94 case "msupackagepayload":
95 harvesterExtension = new PayloadHarvester(this.PayloadHarvester, WixBundlePackageType.Msu);
96 active = true;
97 break;
98 case "perf": 90 case "perf":
99 harvesterExtension = new PerformanceCategoryHarvester(); 91 harvesterExtension = new PerformanceCategoryHarvester();
100 active = true; 92 active = true;
diff --git a/src/wix/test/WixToolsetTest.Converters.Symbolizer/WixToolsetTest.Converters.Symbolizer.v3.ncrunchproject b/src/wix/test/WixToolsetTest.Converters.Symbolizer/WixToolsetTest.Converters.Symbolizer.v3.ncrunchproject
index 18ab4f79..b8c570ae 100644
--- a/src/wix/test/WixToolsetTest.Converters.Symbolizer/WixToolsetTest.Converters.Symbolizer.v3.ncrunchproject
+++ b/src/wix/test/WixToolsetTest.Converters.Symbolizer/WixToolsetTest.Converters.Symbolizer.v3.ncrunchproject
@@ -1,5 +1,8 @@
1<ProjectConfiguration> 1<ProjectConfiguration>
2 <Settings> 2 <Settings>
3 <HiddenComponentWarnings>
4 <Value>LostReference</Value>
5 </HiddenComponentWarnings>
3 <IgnoredTests> 6 <IgnoredTests>
4 <NamedTestSelector> 7 <NamedTestSelector>
5 <TestName>WixToolsetTest.Converters.Symbolizer.ConvertSymbolsFixture.CanLoadWixoutAndConvertToIntermediate</TestName> 8 <TestName>WixToolsetTest.Converters.Symbolizer.ConvertSymbolsFixture.CanLoadWixoutAndConvertToIntermediate</TestName>
diff --git a/src/wix/test/WixToolsetTest.Core.Native/CertificateHashesFixture.cs b/src/wix/test/WixToolsetTest.Core.Native/CertificateHashesFixture.cs
new file mode 100644
index 00000000..5783445b
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Core.Native/CertificateHashesFixture.cs
@@ -0,0 +1,58 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreNative
4{
5 using System.ComponentModel;
6 using System.Linq;
7 using WixToolset.Core.Native;
8 using WixToolsetTest.CoreNative.Utility;
9 using Xunit;
10
11 public class CertificateHashesFixture
12 {
13 [Fact]
14 public void CanGetHashesFromSignedFile()
15 {
16 var cabFile = TestData.Get(@"TestData\test.cab");
17
18 var hashes = CertificateHashes.Read(new[] { cabFile });
19
20 var hash = hashes.Single();
21 Assert.Equal(cabFile, hash.Path);
22 Assert.Equal("7EC90B3FC3D580EB571210011F1095E149DCC6BB", hash.PublicKey);
23 Assert.Equal("0B13494DB50BC185A34389BBBAA01EDD1CF56350", hash.Thumbprint);
24 Assert.Null(hash.Exception);
25 }
26
27 [Fact]
28 public void CannotGetHashesFromUnsignedFile()
29 {
30 var txtFile = TestData.Get(@"TestData\test.txt");
31
32 var hashes = CertificateHashes.Read(new[] { txtFile });
33
34 var hash = hashes.Single();
35 Assert.Equal(txtFile, hash.Path);
36 Assert.Null(hash.Exception);
37 }
38
39 [Fact]
40 public void CanGetMultipleHashes()
41 {
42 var cabFile = TestData.Get(@"TestData\test.cab");
43 var txtFile = TestData.Get(@"TestData\test.txt");
44
45 var hashes = CertificateHashes.Read(new[] { cabFile, txtFile });
46
47 Assert.Equal(cabFile, hashes[0].Path);
48 Assert.Equal("7EC90B3FC3D580EB571210011F1095E149DCC6BB", hashes[0].PublicKey);
49 Assert.Equal("0B13494DB50BC185A34389BBBAA01EDD1CF56350", hashes[0].Thumbprint);
50 Assert.Null(hashes[0].Exception);
51
52 Assert.Equal(txtFile, hashes[1].Path);
53 Assert.Empty(hashes[1].PublicKey);
54 Assert.Empty(hashes[1].Thumbprint);
55 Assert.Null(hashes[1].Exception);
56 }
57 }
58}
diff --git a/src/wix/test/WixToolsetTest.Core.Native/TestData/test.cab b/src/wix/test/WixToolsetTest.Core.Native/TestData/test.cab
index ca78f632..9700cd64 100644
--- a/src/wix/test/WixToolsetTest.Core.Native/TestData/test.cab
+++ b/src/wix/test/WixToolsetTest.Core.Native/TestData/test.cab
Binary files differ
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs
index 6b2d8bfa..5d0c8561 100644
--- a/src/wix/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs
@@ -57,6 +57,26 @@ namespace WixToolsetTest.CoreIntegration
57 } 57 }
58 58
59 [Fact] 59 [Fact]
60 public void CanSpecifyPackagePayloadWithCertificate()
61 {
62 var folder = TestData.Get(@"TestData", "PackagePayload");
63
64 using (var fs = new DisposableFileSystem())
65 {
66 var baseFolder = fs.GetFolder();
67
68 var result = WixRunner.Execute(new[]
69 {
70 "build",
71 Path.Combine(folder, "SpecifiedCertificate.wxs"),
72 "-o", Path.Combine(baseFolder, "test.wixlib")
73 });
74
75 result.AssertSuccess();
76 }
77 }
78
79 [Fact]
60 public void ErrorWhenMissingSourceFileAndHash() 80 public void ErrorWhenMissingSourceFileAndHash()
61 { 81 {
62 var folder = TestData.Get(@"TestData", "PackagePayload"); 82 var folder = TestData.Get(@"TestData", "PackagePayload");
@@ -75,7 +95,7 @@ namespace WixToolsetTest.CoreIntegration
75 Assert.Equal(44, result.ExitCode); 95 Assert.Equal(44, result.ExitCode);
76 WixAssert.CompareLineByLine(new[] 96 WixAssert.CompareLineByLine(new[]
77 { 97 {
78 "The MsuPackagePayload element's SourceFile or Hash attribute was not found; one of these is required.", 98 "The MsuPackagePayload element's SourceFile, CertificatePublicKey, or Hash attribute was not found; one of these is required.",
79 }, result.Messages.Select(m => m.ToString()).ToArray()); 99 }, result.Messages.Select(m => m.ToString()).ToArray());
80 } 100 }
81 } 101 }
@@ -144,10 +164,10 @@ namespace WixToolsetTest.CoreIntegration
144 "-o", Path.Combine(baseFolder, "test.wixlib") 164 "-o", Path.Combine(baseFolder, "test.wixlib")
145 }); 165 });
146 166
147 Assert.Equal(10, result.ExitCode); 167 Assert.Equal(408, result.ExitCode);
148 WixAssert.CompareLineByLine(new[] 168 WixAssert.CompareLineByLine(new[]
149 { 169 {
150 "The MsuPackagePayload/@DownloadUrl attribute was not found; it is required when attribute Hash is specified.", 170 "The MsuPackagePayload element's DownloadUrl attribute was not found; it is required without attribute SourceFile present.",
151 }, result.Messages.Select(m => m.ToString()).ToArray()); 171 }, result.Messages.Select(m => m.ToString()).ToArray());
152 } 172 }
153 } 173 }
@@ -177,6 +197,102 @@ namespace WixToolsetTest.CoreIntegration
177 } 197 }
178 198
179 [Fact] 199 [Fact]
200 public void ErrorWhenSpecifiedSourceFileAndCertificatePublicKey()
201 {
202 var folder = TestData.Get(@"TestData", "PackagePayload");
203
204 using (var fs = new DisposableFileSystem())
205 {
206 var baseFolder = fs.GetFolder();
207
208 var result = WixRunner.Execute(new[]
209 {
210 "build",
211 Path.Combine(folder, "SpecifiedSourceFileAndCertificatePublicKey.wxs"),
212 "-o", Path.Combine(baseFolder, "test.wixlib")
213 });
214
215 WixAssert.CompareLineByLine(new[]
216 {
217 "The ExePackagePayload/@CertificatePublicKey attribute cannot be specified when attribute SourceFile is present.",
218 }, result.Messages.Select(m => m.ToString()).ToArray());
219 Assert.Equal(35, result.ExitCode);
220 }
221 }
222
223 [Fact]
224 public void ErrorWhenSpecifiedSourceFileAndCertificateThumprint()
225 {
226 var folder = TestData.Get(@"TestData", "PackagePayload");
227
228 using (var fs = new DisposableFileSystem())
229 {
230 var baseFolder = fs.GetFolder();
231
232 var result = WixRunner.Execute(new[]
233 {
234 "build",
235 Path.Combine(folder, "SpecifiedSourceFileAndCertificateThumbprint.wxs"),
236 "-o", Path.Combine(baseFolder, "test.wixlib")
237 });
238
239 WixAssert.CompareLineByLine(new[]
240 {
241 "The ExePackagePayload/@CertificateThumbprint attribute cannot be specified when attribute SourceFile is present.",
242 }, result.Messages.Select(m => m.ToString()).ToArray());
243 Assert.Equal(35, result.ExitCode);
244 }
245 }
246
247 [Fact]
248 public void ErrorWhenSpecifiedCertificateThumbprintWithoutPublicKey()
249 {
250 var folder = TestData.Get(@"TestData", "PackagePayload");
251
252 using (var fs = new DisposableFileSystem())
253 {
254 var baseFolder = fs.GetFolder();
255
256 var result = WixRunner.Execute(new[]
257 {
258 "build",
259 Path.Combine(folder, "SpecifiedHashAndCertificatePublicKey.wxs"),
260 "-o", Path.Combine(baseFolder, "test.wixlib")
261 });
262
263 WixAssert.CompareLineByLine(new[]
264 {
265 "The ExePackagePayload/@CertificatePublicKey attribute was not found; it is required when attribute CertificateThumbprint is specified.",
266 }, result.Messages.Select(m => m.ToString()).ToArray());
267 Assert.Equal(10, result.ExitCode);
268 }
269 }
270
271 [Fact]
272 public void ErrorWhenSpecifiedCertificatePublicKeyWithoutThumbprint()
273 {
274 var folder = TestData.Get(@"TestData", "PackagePayload");
275
276 using (var fs = new DisposableFileSystem())
277 {
278 var baseFolder = fs.GetFolder();
279
280 var result = WixRunner.Execute(new[]
281 {
282 "build",
283 Path.Combine(folder, "SpecifiedCertificatePublicKeyWithoutThumbprint.wxs"),
284 "-o", Path.Combine(baseFolder, "test.wixlib")
285 });
286
287 WixAssert.CompareLineByLine(new[]
288 {
289 "The ExePackagePayload/@CertificateThumbprint attribute was not found; it is required when attribute CertificatePublicKey is specified.",
290 }, result.Messages.Select(m => m.ToString()).ToArray());
291 Assert.Equal(10, result.ExitCode);
292 }
293 }
294
295 [Fact]
180 public void ErrorWhenWrongPackagePayloadInPayloadGroup() 296 public void ErrorWhenWrongPackagePayloadInPayloadGroup()
181 { 297 {
182 var folder = TestData.Get(@"TestData"); 298 var folder = TestData.Get(@"TestData");
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/RemotePayloadFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/RemotePayloadFixture.cs
new file mode 100644
index 00000000..434b3621
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/RemotePayloadFixture.cs
@@ -0,0 +1,172 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System.IO;
6 using System.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Core.TestPackage;
9 using Xunit;
10
11 public class RemotePayloadFixture
12 {
13 [Fact]
14 public void CanGetRemotePayload()
15 {
16 var folder = TestData.Get(@"TestData");
17
18 using (var fs = new DisposableFileSystem())
19 {
20 var outputFolder = fs.GetFolder();
21 var outFile = Path.Combine(outputFolder, "out.xml");
22
23 var result = WixRunner.Execute(new[]
24 {
25 "burn", "remotepayload",
26 Path.Combine(folder, ".Data", "burn.exe"),
27 "-downloadurl", "https://www.example.com/files/{0}",
28 "-o", outFile
29 });
30
31 result.AssertSuccess();
32
33 var elements = File.ReadAllLines(outFile);
34 elements = elements.Select(s => s.Replace("\"", "'")).ToArray();
35
36 WixAssert.CompareLineByLine(new[]
37 {
38 @"<ExePackagePayload Name='burn.exe' ProductName='Windows Installer XML Toolset' Description='WiX Toolset Bootstrapper' DownloadUrl='https://www.example.com/files/burn.exe' Hash='F6E722518AC3AB7E31C70099368D5770788C179AA23226110DCF07319B1E1964E246A1E8AE72E2CF23E0138AFC281BAFDE45969204405E114EB20C8195DA7E5E' Size='463360' Version='3.14.1703.0' />",
39 }, elements);
40 }
41 }
42
43 [Fact]
44 public void CanGetRemoteMsuPayload()
45 {
46 var folder = TestData.Get(@"TestData");
47
48 using (var fs = new DisposableFileSystem())
49 {
50 var outputFolder = fs.GetFolder();
51 var outFile = Path.Combine(outputFolder, "out.xml");
52
53 var result = WixRunner.Execute(new[]
54 {
55 "burn", "remotepayload",
56 Path.Combine(folder, ".Data", "Windows8.1-KB2937592-x86.msu"),
57 "-o", outFile
58 });
59
60 result.AssertSuccess();
61
62 var elements = File.ReadAllLines(outFile);
63 elements = elements.Select(s => s.Replace("\"", "'")).ToArray();
64
65 WixAssert.CompareLineByLine(new[]
66 {
67 @"<MsuPackagePayload Name='Windows8.1-KB2937592-x86.msu' Hash='904ADEA6AB675ACE16483138BF3F5850FD56ACB6E3A13AFA7263ED49C68CCE6CF84D6AAD6F99AAF175A95EE1A56C787C5AD968019056490B1073E7DBB7B9B7BE' Size='309544' />",
68 }, elements);
69 }
70 }
71
72 [Fact]
73 public void CanGetRemotePayloadWithCertificate()
74 {
75 var folder = TestData.Get(@"TestData");
76
77 using (var fs = new DisposableFileSystem())
78 {
79 var outputFolder = fs.GetFolder();
80 var outFile = Path.Combine(outputFolder, "out.xml");
81
82 var result = WixRunner.Execute(new[]
83 {
84 "burn", "remotepayload",
85 "-usecertificate",
86 Path.Combine(folder, ".Data", "burn.exe"),
87 Path.Combine(folder, ".Data", "signed_cab1.cab"),
88 "-o", outFile
89 });
90
91 result.AssertSuccess();
92
93 var elements = File.ReadAllLines(outFile);
94 elements = elements.Select(s => s.Replace("\"", "'")).ToArray();
95
96 WixAssert.CompareLineByLine(new[]
97 {
98 @"<ExePackagePayload Name='burn.exe' ProductName='Windows Installer XML Toolset' Description='WiX Toolset Bootstrapper' Hash='F6E722518AC3AB7E31C70099368D5770788C179AA23226110DCF07319B1E1964E246A1E8AE72E2CF23E0138AFC281BAFDE45969204405E114EB20C8195DA7E5E' Size='463360' Version='3.14.1703.0' />",
99 @"<Payload Name='signed_cab1.cab' CertificatePublicKey='BBD1B48A37503767C71F455624967D406A5D66C3' CertificateThumbprint='DE13B4CE635E3F63AA2394E66F95C460267BC82F' Hash='D8D3842403710E1F6036A62543224855CADF546853933C2B17BA99D789D4347B36717687C022678A9D3DE749DFC1482DAAB92B997B62BB32A8A6828B9D04C414' Size='1585' />",
100 }, elements);
101 }
102 }
103
104 [Fact]
105 public void CanGetRemotePayloadWithoutCertificate()
106 {
107 var folder = TestData.Get(@"TestData");
108
109 using (var fs = new DisposableFileSystem())
110 {
111 var outputFolder = fs.GetFolder();
112 var outFile = Path.Combine(outputFolder, "out.xml");
113
114 var result = WixRunner.Execute(new[]
115 {
116 "burn", "remotepayload",
117 Path.Combine(folder, ".Data", "burn.exe"),
118 Path.Combine(folder, ".Data", "signed_cab1.cab"),
119 "-o", outFile
120 });
121
122 result.AssertSuccess();
123
124 var elements = File.ReadAllLines(outFile);
125 elements = elements.Select(s => s.Replace("\"", "'")).ToArray();
126
127 WixAssert.CompareLineByLine(new[]
128 {
129 @"<ExePackagePayload Name='burn.exe' ProductName='Windows Installer XML Toolset' Description='WiX Toolset Bootstrapper' Hash='F6E722518AC3AB7E31C70099368D5770788C179AA23226110DCF07319B1E1964E246A1E8AE72E2CF23E0138AFC281BAFDE45969204405E114EB20C8195DA7E5E' Size='463360' Version='3.14.1703.0' />",
130 @"<Payload Name='signed_cab1.cab' Hash='D8D3842403710E1F6036A62543224855CADF546853933C2B17BA99D789D4347B36717687C022678A9D3DE749DFC1482DAAB92B997B62BB32A8A6828B9D04C414' Size='1585' />",
131 }, elements);
132 }
133 }
134
135 [Fact]
136 public void CanGetRemotePayloadsRecursive()
137 {
138 var folder = TestData.Get(@"TestData");
139
140 using (var fs = new DisposableFileSystem())
141 {
142 var outputFolder = fs.GetFolder();
143 var outFile = Path.Combine(outputFolder, "out.xml");
144
145 var result = WixRunner.Execute(new[]
146 {
147 "burn", "remotepayload",
148 "-recurse",
149 "-du", "https://www.example.com/files/{0}",
150 Path.Combine(folder, ".Data", "burn.exe"),
151 Path.Combine(folder, "RemotePayload", "*"),
152 "-basepath", folder,
153 "-bp", Path.Combine(folder, ".Data"),
154 "-o", outFile
155 });
156
157 result.AssertSuccess();
158
159 var elements = File.ReadAllLines(outFile);
160 elements = elements.Select(s => s.Replace("\"", "'")).ToArray();
161
162 WixAssert.CompareLineByLine(new[]
163 {
164 @"<ExePackagePayload Name='burn.exe' ProductName='Windows Installer XML Toolset' Description='WiX Toolset Bootstrapper' DownloadUrl='https://www.example.com/files/burn.exe' Hash='F6E722518AC3AB7E31C70099368D5770788C179AA23226110DCF07319B1E1964E246A1E8AE72E2CF23E0138AFC281BAFDE45969204405E114EB20C8195DA7E5E' Size='463360' Version='3.14.1703.0' />",
165 @"<Payload Name='a.dat' DownloadUrl='https://www.example.com/files/RemotePayload/a.dat' Hash='D13926E5CBE5ED8B46133F9199FAF2FF25B25981C67A31AE2BC3F6C20390FACBFADCD89BD22D3445D95B989C8EACFB1E68DB634BECB5C9624865BA453BCE362A' Size='16' />",
166 @"<Payload Name='b.dat' DownloadUrl='https://www.example.com/files/RemotePayload/subfolder/b.dat' Hash='5F94707BC29ADFE3B9615E6753388707FD0B8F5FD9EEEC2B17E21E72F1635FF7D7A101E7D14F614E111F263CB9AC4D0940BE1247881A7844F226D6C400293D8E' Size='37' />",
167 @"<Payload Name='c.dat' DownloadUrl='https://www.example.com/files/RemotePayload/subfolder/c.dat' Hash='97D6209A5571E05E4F72F9C6BF0987651FA03E63F971F9B53C2B3D798A666D9864F232D4E2D6442E47D9D72B282309B6EEFF4EE017B43B706FA92A0F5EF74734' Size='42' />",
168 }, elements);
169 }
170 }
171 }
172}
diff --git a/src/wix/test/WixToolsetTest.Heat/TestData/.Data/Windows8.1-KB2937592-x86.msu b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/.Data/Windows8.1-KB2937592-x86.msu
index c39f53b0..c39f53b0 100644
--- a/src/wix/test/WixToolsetTest.Heat/TestData/.Data/Windows8.1-KB2937592-x86.msu
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/.Data/Windows8.1-KB2937592-x86.msu
Binary files differ
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedCertificate.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedCertificate.wxs
new file mode 100644
index 00000000..b5dec9a2
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedCertificate.wxs
@@ -0,0 +1,10 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <ExePackage Id="SpecifiedSourceFileAndHash" Permanent="yes" DetectCondition="none">
6 <ExePackagePayload CertificatePublicKey="abcd" CertificateThumbprint="abcd" Hash="1234" DownloadUrl="https://example.com/" Name="fake.exe" Size="100" />
7 </ExePackage>
8 </PackageGroup>
9 </Fragment>
10</Wix>
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedCertificatePublicKeyWithoutThumbprint.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedCertificatePublicKeyWithoutThumbprint.wxs
new file mode 100644
index 00000000..aa915a31
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedCertificatePublicKeyWithoutThumbprint.wxs
@@ -0,0 +1,10 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <ExePackage Id="SpecifiedSourceFileAndHash" Permanent="yes" DetectCondition="none">
6 <ExePackagePayload CertificatePublicKey="abcd" Hash="123" DownloadUrl="https://example.com/" Name="fake.exe" Size="100" />
7 </ExePackage>
8 </PackageGroup>
9 </Fragment>
10</Wix>
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndCertificatePublicKey.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndCertificatePublicKey.wxs
new file mode 100644
index 00000000..5570017e
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndCertificatePublicKey.wxs
@@ -0,0 +1,10 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <ExePackage Id="SpecifiedSourceFileAndHash" Permanent="yes" DetectCondition="none">
6 <ExePackagePayload Hash="abcd" CertificateThumbprint="abcd" DownloadUrl="https://example.com/" Name="fake.exe" Size="100" />
7 </ExePackage>
8 </PackageGroup>
9 </Fragment>
10</Wix>
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndCertificatePublicKey.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndCertificatePublicKey.wxs
new file mode 100644
index 00000000..b79b5596
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndCertificatePublicKey.wxs
@@ -0,0 +1,10 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <ExePackage Id="SpecifiedSourceFileAndHash" Permanent="yes" DetectCondition="none">
6 <ExePackagePayload SourceFile="example.exe" CertificatePublicKey="abcd" />
7 </ExePackage>
8 </PackageGroup>
9 </Fragment>
10</Wix>
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndCertificateThumbprint.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndCertificateThumbprint.wxs
new file mode 100644
index 00000000..ad2ab521
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndCertificateThumbprint.wxs
@@ -0,0 +1,10 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <ExePackage Id="SpecifiedSourceFileAndHash" Permanent="yes" DetectCondition="none">
6 <ExePackagePayload SourceFile="example.exe" CertificateThumbprint="abcd" />
7 </ExePackage>
8 </PackageGroup>
9 </Fragment>
10</Wix>
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/a.dat b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/a.dat
new file mode 100644
index 00000000..a96b19b3
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/a.dat
@@ -0,0 +1 @@
This is a.dat.
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/subfolder/b.dat b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/subfolder/b.dat
new file mode 100644
index 00000000..35c4c043
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/subfolder/b.dat
@@ -0,0 +1 @@
This is b.dat. A little bit longer.
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/subfolder/c.dat b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/subfolder/c.dat
new file mode 100644
index 00000000..937aa91f
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/subfolder/c.dat
@@ -0,0 +1 @@
This is c.dat. A little bit longer, now!
diff --git a/src/wix/test/WixToolsetTest.Heat/PayloadTests.cs b/src/wix/test/WixToolsetTest.Heat/PayloadTests.cs
deleted file mode 100644
index 8072f50d..00000000
--- a/src/wix/test/WixToolsetTest.Heat/PayloadTests.cs
+++ /dev/null
@@ -1,66 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.Harvesters
4{
5 using System;
6 using System.IO;
7 using WixBuildTools.TestSupport;
8 using Xunit;
9
10 public class PayloadTests
11 {
12 [Fact]
13 public void CanHarvestExePackagePayload()
14 {
15 var folder = TestData.Get(@"TestData");
16
17 using (var fs = new DisposableFileSystem())
18 {
19 var baseFolder = fs.GetFolder();
20 var outputFilePath = Path.Combine(baseFolder, "test.wxs");
21
22 var result = HeatRunner.Execute(new[]
23 {
24 "exepackagepayload",
25 Path.Combine(folder, ".Data", "burn.exe"),
26 "-o", outputFilePath,
27 });
28
29 result.AssertSuccess();
30
31 Assert.True(File.Exists(outputFilePath));
32
33 var expected = File.ReadAllText(Path.Combine(folder, "Payload", "HarvestedExePackagePayload.wxs")).Replace("\r\n", "\n");
34 var actual = File.ReadAllText(outputFilePath).Replace("\r\n", "\n");
35 Assert.Equal(expected, actual);
36 }
37 }
38
39 [Fact]
40 public void CanHarvestMsuPackagePayload()
41 {
42 var folder = TestData.Get(@"TestData");
43
44 using (var fs = new DisposableFileSystem())
45 {
46 var baseFolder = fs.GetFolder();
47 var outputFilePath = Path.Combine(baseFolder, "test.wxs");
48
49 var result = HeatRunner.Execute(new[]
50 {
51 "msupackagepayload",
52 Path.Combine(folder, ".Data", "Windows8.1-KB2937592-x86.msu"),
53 "-o", outputFilePath,
54 });
55
56 result.AssertSuccess();
57
58 Assert.True(File.Exists(outputFilePath));
59
60 var expected = File.ReadAllText(Path.Combine(folder, "Payload", "HarvestedMsuPackagePayload.wxs")).Replace("\r\n", "\n");
61 var actual = File.ReadAllText(outputFilePath).Replace("\r\n", "\n");
62 Assert.Equal(expected, actual);
63 }
64 }
65 }
66}
diff --git a/src/wix/test/WixToolsetTest.Heat/TestData/.Data/burn.exe b/src/wix/test/WixToolsetTest.Heat/TestData/.Data/burn.exe
deleted file mode 100644
index 2a4f423f..00000000
--- a/src/wix/test/WixToolsetTest.Heat/TestData/.Data/burn.exe
+++ /dev/null
Binary files differ
diff --git a/src/wix/test/WixToolsetTest.Heat/TestData/Payload/HarvestedExePackagePayload.wxs b/src/wix/test/WixToolsetTest.Heat/TestData/Payload/HarvestedExePackagePayload.wxs
deleted file mode 100644
index 40100f22..00000000
--- a/src/wix/test/WixToolsetTest.Heat/TestData/Payload/HarvestedExePackagePayload.wxs
+++ /dev/null
@@ -1,6 +0,0 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ExePackagePayload Description="WiX Toolset Bootstrapper" Hash="F6E722518AC3AB7E31C70099368D5770788C179AA23226110DCF07319B1E1964E246A1E8AE72E2CF23E0138AFC281BAFDE45969204405E114EB20C8195DA7E5E" ProductName="Windows Installer XML Toolset" Size="463360" Version="3.14.1703.0" />
5 </Fragment>
6</Wix> \ No newline at end of file
diff --git a/src/wix/test/WixToolsetTest.Heat/TestData/Payload/HarvestedMsuPackagePayload.wxs b/src/wix/test/WixToolsetTest.Heat/TestData/Payload/HarvestedMsuPackagePayload.wxs
deleted file mode 100644
index f203fe27..00000000
--- a/src/wix/test/WixToolsetTest.Heat/TestData/Payload/HarvestedMsuPackagePayload.wxs
+++ /dev/null
@@ -1,6 +0,0 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <MsuPackagePayload Hash="904ADEA6AB675ACE16483138BF3F5850FD56ACB6E3A13AFA7263ED49C68CCE6CF84D6AAD6F99AAF175A95EE1A56C787C5AD968019056490B1073E7DBB7B9B7BE" Size="309544" />
5 </Fragment>
6</Wix> \ No newline at end of file
diff --git a/src/wix/test/WixToolsetTest.Heat/WixToolsetTest.Heat.csproj b/src/wix/test/WixToolsetTest.Heat/WixToolsetTest.Heat.csproj
index 2a706aa0..1ba62393 100644
--- a/src/wix/test/WixToolsetTest.Heat/WixToolsetTest.Heat.csproj
+++ b/src/wix/test/WixToolsetTest.Heat/WixToolsetTest.Heat.csproj
@@ -9,10 +9,6 @@
9 </PropertyGroup> 9 </PropertyGroup>
10 10
11 <ItemGroup> 11 <ItemGroup>
12 <Content Include="TestData\**" CopyToOutputDirectory="PreserveNewest" />
13 </ItemGroup>
14
15 <ItemGroup>
16 <ProjectReference Include="..\..\heat\heat.csproj" /> 12 <ProjectReference Include="..\..\heat\heat.csproj" />
17 <ProjectReference Include="..\..\WixToolset.Core.TestPackage\WixToolset.Core.TestPackage.csproj" /> 13 <ProjectReference Include="..\..\WixToolset.Core.TestPackage\WixToolset.Core.TestPackage.csproj" />
18 </ItemGroup> 14 </ItemGroup>
diff --git a/src/wix/wixnative/certhashes.cpp b/src/wix/wixnative/certhashes.cpp
new file mode 100644
index 00000000..cbb548ba
--- /dev/null
+++ b/src/wix/wixnative/certhashes.cpp
@@ -0,0 +1,138 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5#define SHA1_HASH_LEN 20
6
7static HRESULT GetPublicKeyIdentifierAndThumbprint(
8 __in_z LPCWSTR wzPath,
9 __inout_z LPWSTR* psczPublicKeyIdentifier,
10 __inout_z LPWSTR* psczThumbprint);
11
12static HRESULT GetChainContext(
13 __in_z LPCWSTR wzPath,
14 __out PCCERT_CHAIN_CONTEXT* ppChainContext);
15
16
17HRESULT CertificateHashesCommand(
18 __in int argc,
19 __in_ecount(argc) LPWSTR argv[])
20{
21 Unused(argc);
22 Unused(argv);
23
24 HRESULT hr = S_OK;
25
26 LPWSTR sczFilePath = NULL;
27 LPWSTR sczPublicKeyIdentifier = NULL;
28 LPWSTR sczThumbprint = NULL;
29
30 hr = WixNativeReadStdinPreamble();
31 ExitOnFailure(hr, "Failed to read stdin preamble before reading paths to get certificate hashes");
32
33 // Get the hash for each provided file.
34 for (;;)
35 {
36 hr = ConsoleReadW(&sczFilePath);
37 ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "Failed to read file path to signed file from stdin");
38
39 if (!*sczFilePath)
40 {
41 break;
42 }
43
44 hr = GetPublicKeyIdentifierAndThumbprint(sczFilePath, &sczPublicKeyIdentifier, &sczThumbprint);
45 if (FAILED(hr))
46 {
47 // Treat no signature as success without finding certificate hashes.
48 ConsoleWriteLine(CONSOLE_COLOR_NORMAL, "%ls\t\t\t0x%x", sczFilePath, TRUST_E_NOSIGNATURE == hr ? 0 : hr);
49 }
50 else
51 {
52 ConsoleWriteLine(CONSOLE_COLOR_NORMAL, "%ls\t%ls\t%ls\t0x%x", sczFilePath, sczPublicKeyIdentifier, sczThumbprint, hr);
53 }
54 }
55
56LExit:
57 ReleaseStr(sczThumbprint);
58 ReleaseStr(sczPublicKeyIdentifier);
59 ReleaseStr(sczFilePath);
60
61 return hr;
62}
63
64static HRESULT GetPublicKeyIdentifierAndThumbprint(
65 __in_z LPCWSTR wzPath,
66 __inout_z LPWSTR* psczPublicKeyIdentifier,
67 __inout_z LPWSTR* psczThumbprint)
68{
69 HRESULT hr = S_OK;
70 PCCERT_CHAIN_CONTEXT pChainContext = NULL;
71 PCCERT_CONTEXT pCertContext = NULL;
72 BYTE rgbPublicKeyIdentifier[SHA1_HASH_LEN] = { };
73 DWORD cbPublicKeyIdentifier = sizeof(rgbPublicKeyIdentifier);
74 BYTE* pbThumbprint = NULL;
75 DWORD cbThumbprint = 0;
76
77 hr = GetChainContext(wzPath, &pChainContext);
78 ExitOnFailure(hr, "Failed to get chain context for file: %ls", wzPath);
79
80 pCertContext = pChainContext->rgpChain[0]->rgpElement[0]->pCertContext;
81
82 // Get the certificate's public key identifier and thumbprint.
83 if (!::CryptHashPublicKeyInfo(NULL, CALG_SHA1, 0, X509_ASN_ENCODING, &pCertContext->pCertInfo->SubjectPublicKeyInfo, rgbPublicKeyIdentifier, &cbPublicKeyIdentifier))
84 {
85 ExitWithLastError(hr, "Failed to get certificate public key identifier from file: %ls", wzPath);
86 }
87
88 hr = CertReadProperty(pCertContext, CERT_SHA1_HASH_PROP_ID, &pbThumbprint, &cbThumbprint);
89 ExitOnFailure(hr, "Failed to read certificate thumbprint from file: %ls", wzPath);
90
91 // Get the public key indentifier and thumbprint in hex.
92 hr = StrAllocHexEncode(rgbPublicKeyIdentifier, cbPublicKeyIdentifier, psczPublicKeyIdentifier);
93 ExitOnFailure(hr, "Failed to convert certificate public key to hex for file: %ls", wzPath);
94
95 hr = StrAllocHexEncode(pbThumbprint, cbThumbprint, psczThumbprint);
96 ExitOnFailure(hr, "Failed to convert certificate thumbprint to hex for file: %ls", wzPath);
97
98LExit:
99 ReleaseMem(pbThumbprint);
100 return hr;
101}
102
103static HRESULT GetChainContext(
104 __in_z LPCWSTR wzPath,
105 __out PCCERT_CHAIN_CONTEXT* ppChainContext)
106{
107 HRESULT hr = S_OK;
108
109 GUID guidAuthenticode = WINTRUST_ACTION_GENERIC_VERIFY_V2;
110 WINTRUST_FILE_INFO wfi = { };
111 WINTRUST_DATA wtd = { };
112 CRYPT_PROVIDER_DATA* pProviderData = NULL;
113 CRYPT_PROVIDER_SGNR* pSigner = NULL;
114
115 wfi.cbStruct = sizeof(wfi);
116 wfi.pcwszFilePath = wzPath;
117
118 wtd.cbStruct = sizeof(wtd);
119 wtd.dwUnionChoice = WTD_CHOICE_FILE;
120 wtd.pFile = &wfi;
121 wtd.dwStateAction = WTD_STATEACTION_VERIFY;
122 wtd.dwProvFlags = WTD_REVOCATION_CHECK_NONE | WTD_HASH_ONLY_FLAG | WTD_CACHE_ONLY_URL_RETRIEVAL;
123 wtd.dwUIChoice = WTD_UI_NONE;
124
125 hr = ::WinVerifyTrust(static_cast<HWND>(INVALID_HANDLE_VALUE), &guidAuthenticode, &wtd);
126 ExitOnFailure(hr, "Failed to verify certificate on file: %ls", wzPath);
127
128 pProviderData = ::WTHelperProvDataFromStateData(wtd.hWVTStateData);
129 ExitOnNullWithLastError(pProviderData, hr, "Failed to get provider state from authenticode certificate on file: %ls", wzPath);
130
131 pSigner = ::WTHelperGetProvSignerFromChain(pProviderData, 0, FALSE, 0);
132 ExitOnNullWithLastError(pSigner, hr, "Failed to get signer chain from authenticode certificate on file: %ls", wzPath);
133
134 *ppChainContext = pSigner->pChainContext;
135
136LExit:
137 return hr;
138}
diff --git a/src/wix/wixnative/precomp.h b/src/wix/wixnative/precomp.h
index 490bbdcf..0a458ca6 100644
--- a/src/wix/wixnative/precomp.h
+++ b/src/wix/wixnative/precomp.h
@@ -4,9 +4,12 @@
4#include <windows.h> 4#include <windows.h>
5#include <aclapi.h> 5#include <aclapi.h>
6#include <mergemod.h> 6#include <mergemod.h>
7#include <softpub.h>
7#include <strsafe.h> 8#include <strsafe.h>
9#include <wintrust.h>
8 10
9#include "dutil.h" 11#include "dutil.h"
12#include "certutil.h"
10#include "conutil.h" 13#include "conutil.h"
11#include "memutil.h" 14#include "memutil.h"
12#include "pathutil.h" 15#include "pathutil.h"
@@ -15,6 +18,7 @@
15#include "cabutil.h" 18#include "cabutil.h"
16 19
17HRESULT WixNativeReadStdinPreamble(); 20HRESULT WixNativeReadStdinPreamble();
21HRESULT CertificateHashesCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]);
18HRESULT SmartCabCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]); 22HRESULT SmartCabCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]);
19HRESULT ResetAclsCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]); 23HRESULT ResetAclsCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]);
20HRESULT EnumCabCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]); 24HRESULT EnumCabCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]);
diff --git a/src/wix/wixnative/wixnative.cpp b/src/wix/wixnative/wixnative.cpp
index d1236da1..8a24d5f1 100644
--- a/src/wix/wixnative/wixnative.cpp
+++ b/src/wix/wixnative/wixnative.cpp
@@ -24,6 +24,10 @@ int __cdecl wmain(int argc, LPWSTR argv[])
24 { 24 {
25 hr = EnumCabCommand(argc - 2, argv + 2); 25 hr = EnumCabCommand(argc - 2, argv + 2);
26 } 26 }
27 else if (CSTR_EQUAL == ::CompareString(LOCALE_INVARIANT, NORM_IGNORECASE, argv[1], -1, L"certhashes", -1))
28 {
29 hr = CertificateHashesCommand(argc - 2, argv + 2);
30 }
27 else if (CSTR_EQUAL == ::CompareString(LOCALE_INVARIANT, NORM_IGNORECASE, argv[1], -1, L"resetacls", -1)) 31 else if (CSTR_EQUAL == ::CompareString(LOCALE_INVARIANT, NORM_IGNORECASE, argv[1], -1, L"resetacls", -1))
28 { 32 {
29 hr = ResetAclsCommand(argc - 2, argv + 2); 33 hr = ResetAclsCommand(argc - 2, argv + 2);
diff --git a/src/wix/wixnative/wixnative.vcxproj b/src/wix/wixnative/wixnative.vcxproj
index 1dac36e2..2e90661c 100644
--- a/src/wix/wixnative/wixnative.vcxproj
+++ b/src/wix/wixnative/wixnative.vcxproj
@@ -42,7 +42,7 @@
42 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> 42 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
43 43
44 <PropertyGroup> 44 <PropertyGroup>
45 <ProjectAdditionalLinkLibraries>crypt32.lib;cabinet.lib;msi.lib</ProjectAdditionalLinkLibraries> 45 <ProjectAdditionalLinkLibraries>crypt32.lib;cabinet.lib;msi.lib;wintrust.lib</ProjectAdditionalLinkLibraries>
46 </PropertyGroup> 46 </PropertyGroup>
47 47
48 <ItemGroup> 48 <ItemGroup>
@@ -50,6 +50,7 @@
50 <ClCompile Include="precomp.cpp"> 50 <ClCompile Include="precomp.cpp">
51 <PrecompiledHeader>Create</PrecompiledHeader> 51 <PrecompiledHeader>Create</PrecompiledHeader>
52 </ClCompile> 52 </ClCompile>
53 <ClCompile Include="certhashes.cpp" />
53 <ClCompile Include="enumcab.cpp" /> 54 <ClCompile Include="enumcab.cpp" />
54 <ClCompile Include="extractcab.cpp" /> 55 <ClCompile Include="extractcab.cpp" />
55 <ClCompile Include="resetacls.cpp" /> 56 <ClCompile Include="resetacls.cpp" />