diff options
author | Rob Mensching <rob@firegiant.com> | 2022-02-25 22:24:42 -0800 |
---|---|---|
committer | Rob Mensching <rob@firegiant.com> | 2022-02-28 16:56:09 -0800 |
commit | 5fd804165587b3b6d2bd6b9844dcb3fa55a4a305 (patch) | |
tree | 2fe3f82b049734845e8a0f1a0719aabaa8bd3d19 | |
parent | 82bfd1218699a08c8d9cd775fc9e3eef3ec519a2 (diff) | |
download | wix-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".
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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
7 | static HRESULT GetPublicKeyIdentifierAndThumbprint( | ||
8 | __in_z LPCWSTR wzPath, | ||
9 | __inout_z LPWSTR* psczPublicKeyIdentifier, | ||
10 | __inout_z LPWSTR* psczThumbprint); | ||
11 | |||
12 | static HRESULT GetChainContext( | ||
13 | __in_z LPCWSTR wzPath, | ||
14 | __out PCCERT_CHAIN_CONTEXT* ppChainContext); | ||
15 | |||
16 | |||
17 | HRESULT 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 | |||
56 | LExit: | ||
57 | ReleaseStr(sczThumbprint); | ||
58 | ReleaseStr(sczPublicKeyIdentifier); | ||
59 | ReleaseStr(sczFilePath); | ||
60 | |||
61 | return hr; | ||
62 | } | ||
63 | |||
64 | static 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 | |||
98 | LExit: | ||
99 | ReleaseMem(pbThumbprint); | ||
100 | return hr; | ||
101 | } | ||
102 | |||
103 | static 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 | |||
136 | LExit: | ||
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 | ||
17 | HRESULT WixNativeReadStdinPreamble(); | 20 | HRESULT WixNativeReadStdinPreamble(); |
21 | HRESULT CertificateHashesCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]); | ||
18 | HRESULT SmartCabCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]); | 22 | HRESULT SmartCabCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]); |
19 | HRESULT ResetAclsCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]); | 23 | HRESULT ResetAclsCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]); |
20 | HRESULT EnumCabCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]); | 24 | HRESULT 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" /> |