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".
Rob Mensching committed
Feb 25, 2022 at 22:24 UTC
5fd804165587b3b6d2bd6b9844dcb3fa55a4a305
35 files changed
+1033
-247
src/api/wix/WixToolset.Data/Symbols/WixBundlePayloadSymbol.cs
+16
@@ -26,6 +26,8 @@ namespace WixToolset.Data
26
new IntermediateFieldDefinition(nameof(WixBundlePayloadSymbolFields.LayoutOnly), IntermediateFieldType.Bool),
27
new IntermediateFieldDefinition(nameof(WixBundlePayloadSymbolFields.Packaging), IntermediateFieldType.Number),
28
new IntermediateFieldDefinition(nameof(WixBundlePayloadSymbolFields.ParentPackagePayloadRef), IntermediateFieldType.String),
29
+ new IntermediateFieldDefinition(nameof(WixBundlePayloadSymbolFields.CertificatePublicKey), IntermediateFieldType.String),
30
+ new IntermediateFieldDefinition(nameof(WixBundlePayloadSymbolFields.CertificateThumbprint), IntermediateFieldType.String),
31
},
32
typeof(WixBundlePayloadSymbol));
33
}
@@ -53,6 +55,8 @@ namespace WixToolset.Data.Symbols
55
LayoutOnly,
56
Packaging,
57
ParentPackagePayloadRef,
58
+ CertificatePublicKey,
59
+ CertificateThumbprint,
60
}
61
62
public class WixBundlePayloadSymbol : IntermediateSymbol
@@ -162,5 +166,17 @@ namespace WixToolset.Data.Symbols
166
get => (string)this.Fields[(int)WixBundlePayloadSymbolFields.ParentPackagePayloadRef];
167
set => this.Set((int)WixBundlePayloadSymbolFields.ParentPackagePayloadRef, value);
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
+ }
181
}
182
}
src/wix/WixToolset.Converters.Symbolizer/WixToolset.Converters.Symbolizer.v3.ncrunchproject
new
+7
@@ -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
src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs
+10
-1
@@ -672,7 +672,16 @@ namespace WixToolset.Core.Burn.Bundles
672
writer.WriteAttributeString("Id", payload.Id.Id);
673
writer.WriteAttributeString("FilePath", payload.Name);
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
+ }
685
686
if (payload.LayoutOnly)
687
{
src/wix/WixToolset.Core.Burn/BurnBackendErrors.cs
+6
@@ -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);
57
}
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
+
64
private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args)
65
{
66
return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args);
@@ -73,6 +78,7 @@ namespace WixToolset.Core.Burn
78
PackageCachePayloadCollision2 = 8007,
79
TooManyAttachedContainers = 8008,
80
IncompatibleWixBurnSection = 8009,
81
+ UnsupportedRemotePackagePayload = 8010,
82
} // last available is 8499. 8500 is BurnBackendWarnings.
83
}
84
}
src/wix/WixToolset.Core.Burn/CommandLine/BurnCommand.cs
+4
@@ -52,6 +52,10 @@ namespace WixToolset.Core.Burn.CommandLine
52
case "reattach":
53
this.Subcommand = new ReattachSubcommand(this.ServiceProvider);
54
return true;
55
+
56
+ case "remotepayload":
57
+ this.Subcommand = new RemotePayloadSubcommand(this.ServiceProvider);
58
+ return true;
59
}
60
61
return false;
src/wix/WixToolset.Core.Burn/CommandLine/RemotePayloadSubcommand.cs
new
+272
@@ -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
+}
src/wix/WixToolset.Core.Native/CertificateHashes.cs
new
+83
@@ -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
+}
src/wix/WixToolset.Core.Native/WixNativeExe.cs
+1
-1
@@ -30,7 +30,7 @@ namespace WixToolset.Core.Native
30
this.stdinLines.AddRange(lines);
31
}
32
33
- public IEnumerable<string> Run()
33
+ public IReadOnlyCollection<string> Run()
34
{
35
EnsurePathToWixNativeExeSet();
36
src/wix/WixToolset.Core/Compile/CompilerPayload.cs
+67
-22
@@ -11,12 +11,31 @@ namespace WixToolset.Core
11
12
internal class CompilerPayload
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
+
29
public YesNoDefaultType Compressed { get; set; } = YesNoDefaultType.Default;
30
31
public string Description { get; set; }
32
33
public string DownloadUrl { get; set; }
34
35
+ public string CertificatePublicKey { get; set; }
36
+
37
+ public string CertificateThumbprint { get; set; }
38
+
39
public string Hash { get; set; }
40
41
public Identifier Id { get; set; }
@@ -33,24 +52,9 @@ namespace WixToolset.Core
52
53
public string SourceFile { get; set; }
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
-
55
private void CalculateAndVerifyFields()
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));
58
59
if (String.IsNullOrEmpty(this.SourceFile))
60
{
@@ -81,7 +85,7 @@ namespace WixToolset.Core
85
}
86
else
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"));
89
}
90
}
91
}
@@ -93,7 +97,20 @@ namespace WixToolset.Core
97
{
98
if (isRemote)
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
+ }
114
}
115
116
if (!String.IsNullOrEmpty(this.Description))
@@ -120,17 +137,34 @@ namespace WixToolset.Core
137
{
138
if (String.IsNullOrEmpty(this.DownloadUrl))
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"));
141
}
142
143
if (String.IsNullOrEmpty(this.Name))
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"));
146
}
147
148
if (!this.Size.HasValue)
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
+ }
168
}
169
170
if (YesNoDefaultType.Yes == this.Compressed)
@@ -177,6 +211,8 @@ namespace WixToolset.Core
211
Hash = this.Hash,
212
FileSize = this.Size,
213
Version = this.Version,
214
+ CertificatePublicKey = this.CertificatePublicKey,
215
+ CertificateThumbprint = this.CertificateThumbprint
216
});
217
218
this.Core.CreateGroupAndOrderingRows(this.SourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Payload, symbol.Id.Id, ComplexReferenceChildType.Unknown, null);
@@ -248,6 +284,16 @@ namespace WixToolset.Core
284
this.DownloadUrl = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib);
285
}
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
+
297
public void ParseHash(XAttribute attrib)
298
{
299
this.Hash = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib);
@@ -286,6 +332,5 @@ namespace WixToolset.Core
332
{
333
this.Version = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib);
334
}
289
-
335
}
336
}
src/wix/WixToolset.Core/Compiler_Bundle.cs
+14
@@ -2459,6 +2459,20 @@ namespace WixToolset.Core
2459
case "SourceFile":
2460
compilerPayload.ParseSourceFile(attrib);
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;
2476
case "DownloadUrl":
2477
compilerPayload.ParseDownloadUrl(attrib);
2478
break;
src/wix/heat/PayloadHarvester.cs
deleted
-129
@@ -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
-}
src/wix/heat/UtilHeatExtension.cs
-8
@@ -87,14 +87,6 @@ namespace WixToolset.Harvesters
87
harvesterExtension = new FileHarvester();
88
active = true;
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;
90
case "perf":
91
harvesterExtension = new PerformanceCategoryHarvester();
92
active = true;
src/wix/test/WixToolsetTest.Converters.Symbolizer/WixToolsetTest.Converters.Symbolizer.v3.ncrunchproject
+3
@@ -1,5 +1,8 @@
1
<ProjectConfiguration>
2
<Settings>
3
+ <HiddenComponentWarnings>
4
+ <Value>LostReference</Value>
5
+ </HiddenComponentWarnings>
6
<IgnoredTests>
7
<NamedTestSelector>
8
<TestName>WixToolsetTest.Converters.Symbolizer.ConvertSymbolsFixture.CanLoadWixoutAndConvertToIntermediate</TestName>
src/wix/test/WixToolsetTest.Core.Native/CertificateHashesFixture.cs
new
+58
@@ -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
+}
src/wix/test/WixToolsetTest.Core.Native/TestData/test.cab
Binary files a/src/wix/test/WixToolsetTest.Core.Native/TestData/test.cab and b/src/wix/test/WixToolsetTest.Core.Native/TestData/test.cab differ
src/wix/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs
+119
-3
@@ -56,6 +56,26 @@ namespace WixToolsetTest.CoreIntegration
56
}
57
}
58
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]
80
public void ErrorWhenMissingSourceFileAndHash()
81
{
@@ -75,7 +95,7 @@ namespace WixToolsetTest.CoreIntegration
95
Assert.Equal(44, result.ExitCode);
96
WixAssert.CompareLineByLine(new[]
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.",
99
}, result.Messages.Select(m => m.ToString()).ToArray());
100
}
101
}
@@ -144,10 +164,10 @@ namespace WixToolsetTest.CoreIntegration
164
"-o", Path.Combine(baseFolder, "test.wixlib")
165
});
166
147
- Assert.Equal(10, result.ExitCode);
167
+ Assert.Equal(408, result.ExitCode);
168
WixAssert.CompareLineByLine(new[]
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.",
171
}, result.Messages.Select(m => m.ToString()).ToArray());
172
}
173
}
@@ -176,6 +196,102 @@ namespace WixToolsetTest.CoreIntegration
196
}
197
}
198
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]
296
public void ErrorWhenWrongPackagePayloadInPayloadGroup()
297
{
src/wix/test/WixToolsetTest.CoreIntegration/RemotePayloadFixture.cs
new
+172
@@ -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
+}
src/wix/test/WixToolsetTest.CoreIntegration/TestData/.Data/Windows8.1-KB2937592-x86.msu
renamed
src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedCertificate.wxs
new
+10
@@ -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>
src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedCertificatePublicKeyWithoutThumbprint.wxs
new
+10
@@ -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>
src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndCertificatePublicKey.wxs
new
+10
@@ -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>
src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndCertificatePublicKey.wxs
new
+10
@@ -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>
src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndCertificateThumbprint.wxs
new
+10
@@ -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>
src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/a.dat
new
+1
@@ -0,0 +1 @@
1
+This is a.dat.
src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/subfolder/b.dat
new
+1
@@ -0,0 +1 @@
1
+This is b.dat. A little bit longer.
src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/subfolder/c.dat
new
+1
@@ -0,0 +1 @@
1
+This is c.dat. A little bit longer, now!
src/wix/test/WixToolsetTest.Heat/PayloadTests.cs
deleted
-66
@@ -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
-}
src/wix/test/WixToolsetTest.Heat/TestData/.Data/burn.exe
Binary files a/src/wix/test/WixToolsetTest.Heat/TestData/.Data/burn.exe and /dev/null differ
src/wix/test/WixToolsetTest.Heat/TestData/Payload/HarvestedExePackagePayload.wxs
deleted
-6
@@ -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
src/wix/test/WixToolsetTest.Heat/TestData/Payload/HarvestedMsuPackagePayload.wxs
deleted
-6
@@ -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
src/wix/test/WixToolsetTest.Heat/WixToolsetTest.Heat.csproj
-4
@@ -8,10 +8,6 @@
8
<SignOutput>false</SignOutput>
9
</PropertyGroup>
10
11
- <ItemGroup>
12
- <Content Include="TestData\**" CopyToOutputDirectory="PreserveNewest" />
13
- </ItemGroup>
14
-
11
<ItemGroup>
12
<ProjectReference Include="..\..\heat\heat.csproj" />
13
<ProjectReference Include="..\..\WixToolset.Core.TestPackage\WixToolset.Core.TestPackage.csproj" />
src/wix/wixnative/certhashes.cpp
new
+138
@@ -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
+}
src/wix/wixnative/precomp.h
+4
@@ -4,9 +4,12 @@
4
#include <windows.h>
5
#include <aclapi.h>
6
#include <mergemod.h>
7
+#include <softpub.h>
8
#include <strsafe.h>
9
+#include <wintrust.h>
10
11
#include "dutil.h"
12
+#include "certutil.h"
13
#include "conutil.h"
14
#include "memutil.h"
15
#include "pathutil.h"
@@ -15,6 +18,7 @@
18
#include "cabutil.h"
19
20
HRESULT WixNativeReadStdinPreamble();
21
+HRESULT CertificateHashesCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]);
22
HRESULT SmartCabCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]);
23
HRESULT ResetAclsCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]);
24
HRESULT EnumCabCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]);
src/wix/wixnative/wixnative.cpp
+4
@@ -24,6 +24,10 @@ int __cdecl wmain(int argc, LPWSTR argv[])
24
{
25
hr = EnumCabCommand(argc - 2, argv + 2);
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
+ }
31
else if (CSTR_EQUAL == ::CompareString(LOCALE_INVARIANT, NORM_IGNORECASE, argv[1], -1, L"resetacls", -1))
32
{
33
hr = ResetAclsCommand(argc - 2, argv + 2);
src/wix/wixnative/wixnative.vcxproj
+2
-1
@@ -42,7 +42,7 @@
42
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
43
44
<PropertyGroup>
45
- <ProjectAdditionalLinkLibraries>crypt32.lib;cabinet.lib;msi.lib</ProjectAdditionalLinkLibraries>
45
+ <ProjectAdditionalLinkLibraries>crypt32.lib;cabinet.lib;msi.lib;wintrust.lib</ProjectAdditionalLinkLibraries>
46
</PropertyGroup>
47
48
<ItemGroup>
@@ -50,6 +50,7 @@
50
<ClCompile Include="precomp.cpp">
51
<PrecompiledHeader>Create</PrecompiledHeader>
52
</ClCompile>
53
+ <ClCompile Include="certhashes.cpp" />
54
<ClCompile Include="enumcab.cpp" />
55
<ClCompile Include="extractcab.cpp" />
56
<ClCompile Include="resetacls.cpp" />