forked from Azure/azure-powershell
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMsiLayoutTests.cs
274 lines (238 loc) · 13.5 KB
/
MsiLayoutTests.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
namespace PowerShellSetup.Tests
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
/// <summary>
/// Set of tests to run against MSI
/// These set of tests are especially for scenario like:
/// 1) Layout errors, things getting copied to the wrong location
/// 2) Checking if files in the MSI including the MSI are signed
/// 3) Checking if files included in MSI uses right signing alogirthm
///
/// </summary>
public class MsiLayoutTests: MsiTestBase
{
const string MSI_NAME = "AzurePowerShell.msi";
const string MSI_EXTRACT_DIR_NAME = "msiContents";
string _procOutput;
string _procErr;
public MsiLayoutTests(ITestOutputHelper testOutput) : base(testOutput)
{
_procOutput = string.Empty;
_procErr = string.Empty;
}
[Fact]
[Trait("AcceptanceType", "CheckIn")]
public void VerifyMsiExecExists()
{
ProcessStartInfo psi = GetInitializedPSI();
psi.FileName = "Msiexec.exe";
psi.Arguments = "/qn /x:1234";
this.ExecuteShellCmd(psi, out _procOutput, out _procErr);
Assert.NotEmpty(_procOutput);
}
//[Fact]
//[Trait("AcceptanceType", "CheckIn")]
//public void VerifyNoJavaScriptFiles()
//{
// string procErr = string.Empty;
// string msiContentsDirPath = ExtractMsiContents(out procErr);
// IEnumerable<string> msiFiles = Directory.EnumerateFiles(msiContentsDirPath, "*.js", SearchOption.AllDirectories);
// TestLog.WriteLine("Expecting no *.js files in MSI");
// foreach (string unsigFile in msiFiles)
// {
// TestLog.WriteLine(unsigFile);
// }
// Assert.Equal(0, msiFiles.Count<string>());
//}
//[Fact]
//[Trait("AcceptanceType", "CheckIn")]
//public void VerifyNoJsonFiles()
//{
// string procErr = string.Empty;
// string msiContentsDirPath = ExtractMsiContents(out procErr);
// IEnumerable<string> msiFiles = Directory.EnumerateFiles(msiContentsDirPath, "*.json", SearchOption.AllDirectories);
// TestLog.WriteLine("Expecting no *.json files in MSI");
// foreach (string unsigFile in msiFiles)
// {
// TestLog.WriteLine(unsigFile);
// }
// Assert.Equal(0, msiFiles.Count<string>());
//}
[Fact]
[Trait("SignedBuild", "BVT")]
public void VerifyFilesAreSigned()
{
string SHA1 = "sha1RSA";
string SHA2 = "sha256RSA";
List<string> expectedSignatureAlgos = new List<string>() { SHA1, SHA2 };
string msiContentsDir = this.ExtractMsiContents(out _procErr);
Assert.True(Directory.Exists(msiContentsDir));
Assert.True(string.IsNullOrEmpty(_procErr));
IEnumerable<string> msiFiles = Directory.EnumerateFiles(msiContentsDir, "*", SearchOption.AllDirectories);
//Ignore Newtonsoft, .xml, .msi (need to find out why unsigned msi get's packaged inside the MSI)
IEnumerable<string> exceptionFiles = Directory.EnumerateFiles(msiContentsDir, "*.xml", SearchOption.AllDirectories)
.Union<string>(Directory.EnumerateFiles(msiContentsDir, "newtonsoft*.dll", SearchOption.AllDirectories))
.Union<string>(Directory.EnumerateFiles(msiContentsDir, "automapper*.dll", SearchOption.AllDirectories))
.Union<string>(Directory.EnumerateFiles(msiContentsDir, "security*.dll", SearchOption.AllDirectories))
.Union<string>(Directory.EnumerateFiles(msiContentsDir, "bouncy*.dll", SearchOption.AllDirectories))
.Union<string>(Directory.EnumerateFiles(msiContentsDir, "*.psd1", SearchOption.AllDirectories))
.Union<string>(Directory.EnumerateFiles(msiContentsDir, "*.json", SearchOption.AllDirectories))
.Union<string>(Directory.EnumerateFiles(msiContentsDir, "*.cscfg", SearchOption.AllDirectories))
.Union<string>(Directory.EnumerateFiles(msiContentsDir, "*.csdef", SearchOption.AllDirectories))
.Union<string>(Directory.EnumerateFiles(msiContentsDir, "*.cmd", SearchOption.AllDirectories))
.Union<string>(Directory.EnumerateFiles(msiContentsDir, "*.config", SearchOption.AllDirectories))
.Union<string>(Directory.EnumerateFiles(msiContentsDir, "*.php", SearchOption.AllDirectories))
.Union<string>(Directory.EnumerateFiles(msiContentsDir, "*.yml", SearchOption.AllDirectories))
.Union<string>(Directory.EnumerateFiles(msiContentsDir, "*.gitignore", SearchOption.AllDirectories))
.Union<string>(Directory.EnumerateFiles(msiContentsDir, "*.js", SearchOption.AllDirectories)) // We are doing this because Get-AuthenticodeSignature cannot verify .js files
.Union<string>(Directory.EnumerateFiles(msiContentsDir, "*.msi", SearchOption.AllDirectories));
Assert.NotNull(msiFiles);
IEnumerable<string> filesToVerify = msiFiles.Except<string>(exceptionFiles);
#region
// Make sure filesToVerify do not have any of the files that are either external to MS
// or are not signed (which are expected not to be signed eg. psd1 files)
List<string> noXmlFiles = filesToVerify.Where<string>((fl) => fl.EndsWith(".xml")).ToList<string>();
//TestLog.WriteLine("Verifying no .xml files are in the verify list of files");
Assert.True(noXmlFiles.Count == 0);
List<string> noNewtonsoftFiles = filesToVerify.Where<string>((fl) => fl.Contains("newtonsoft")).ToList<string>();
//TestLog.WriteLine("Verifying no 'Newtonsoft*.dll' files are in the verify list of files");
Assert.True(noNewtonsoftFiles.Count == 0);
List<string> noMsiFiles = filesToVerify.Where<string>((fl) => fl.EndsWith(".msi")).ToList<string>();
//TestLog.WriteLine("Verifying no '*.msi' files are in the verify list of files");
Assert.True(noMsiFiles.Count == 0);
#endregion
List<string> noPsd1Files = filesToVerify.Where<string>((fl) => fl.EndsWith(".psd1")).ToList<string>();
//TestLog.WriteLine("Verifying no '*.psd1' files are in the verify list of files");
Assert.True(noPsd1Files.Count == 0);
// Now extract each category of files and verify if they matched to the algorithm they are expected to be signed
IEnumerable<string> dllFiles = filesToVerify.Where<string>((fl) => fl.EndsWith(".dll")).ToList<string>();
IEnumerable<string> scriptFiles = filesToVerify.Where<string>((fl) => fl.EndsWith(".ps1"))
.Union<string>(filesToVerify.Where<string>((fl) => fl.EndsWith(".psm1")))
//.Union<string>(filesToVerify.Where<string>((fl) => fl.EndsWith(".js"))) // We are doing this because Get-AuthenticodeSignature cannot verify .js files
.Union<string>(filesToVerify.Where<string>((fl) => fl.EndsWith(".ps1xml")));
IEnumerable<string> dllScriptCombined = dllFiles.Union<string>(scriptFiles);
IEnumerable<string> diff = filesToVerify.Except<string>(dllScriptCombined);
TestLog.WriteLine("Verify number of dlls, script files match");
foreach(string fl in diff)
{
TestLog.WriteLine(fl);
}
Assert.Equal(dllScriptCombined.Count(), filesToVerify.Count());
List<string> unsignedDlls = GetUnsignedFiles(dllFiles, expectedSignatureAlgos);
TestLog.WriteLine("Verifying if DLLs are properly signed");
unsignedDlls.ForEach((unsigDll) => TestLog.WriteLine(unsigDll));
Assert.Equal(unsignedDlls.Count, 0);
List<string> unsignedScripts = GetUnsignedFiles(scriptFiles, SHA2);
TestLog.WriteLine("Verifying if SCRIPTS are properly signed");
unsignedScripts.ForEach((unsigScript) => TestLog.WriteLine(unsigScript));
Assert.Equal(unsignedScripts.Count, 0);
// We do this because, we sign MSI as SHA2 with SHA1 hash.
// Verifying msi under windows --> Rightclick --> Properties will show SHA1
// Verifying msi with Get-AuthenticodeSignature will return as SHA2
List<string> unsignedMsi = GetUnsignedFiles(new List<string>() { this.GetAzurePSMsiPath() }, SHA2);
TestLog.WriteLine("Verifying if MSI is properly signed");
unsignedMsi.ForEach((unsigMsi) => TestLog.WriteLine(unsigMsi));
Assert.Equal(unsignedMsi.Count, 0);
}
#region Private Functions
private List<string> GetUnsignedFiles(IEnumerable<string> signedFiles, string expectedAlgorithm)
{
List<string> unsignedFiles = new List<string>();
string unsignedFileStatusFormat = "Expected signature '{0}', Actual '{1}' signature ::: {2}";
Parallel.ForEach<string>(signedFiles, (providedFilePath) =>
{
string sigAlgo = GetFileSignature(providedFilePath);
if(string.IsNullOrEmpty(sigAlgo))
{
unsignedFiles.Add(string.Format(unsignedFileStatusFormat, expectedAlgorithm, sigAlgo, providedFilePath));
}
else if(!sigAlgo.Equals(expectedAlgorithm, StringComparison.OrdinalIgnoreCase))
{
unsignedFiles.Add(string.Format(unsignedFileStatusFormat, expectedAlgorithm, sigAlgo, providedFilePath));
}
});
return unsignedFiles;
}
private List<string> GetUnsignedFiles(IEnumerable<string> signedFiles, List<string> expectedSignatureAlgorithmList)
{
List<string> unsignedFiles = new List<string>();
string algoList = string.Join("/", expectedSignatureAlgorithmList);
string unsignedFileStatusFormat = "Expected signature '{0}', Actual '{1}' signature ::: {2}";
Parallel.ForEach<string>(signedFiles, (providedFilePath) =>
{
string sigAlgo = GetFileSignature(providedFilePath);
if (string.IsNullOrEmpty(sigAlgo))
{
unsignedFiles.Add(string.Format(unsignedFileStatusFormat, algoList, sigAlgo, providedFilePath));
}
else
{
string match = expectedSignatureAlgorithmList.Find((pn) => pn.Equals(sigAlgo, System.StringComparison.OrdinalIgnoreCase));
if (string.IsNullOrEmpty(match))
{
unsignedFiles.Add(string.Format(unsignedFileStatusFormat, algoList, sigAlgo, providedFilePath));
}
}
});
return unsignedFiles;
}
private List<string> GetUnsignedFilesSync(IEnumerable<string> signedFiles, string expectedAlgorithm)
{
List<string> unsignedFiles = new List<string>();
string unsignedFileStatusFormat = "Expected signature '{0}', Actual '{1}' signature ::: {2}";
foreach(string providedFilePath in signedFiles)
{
string sigAlgo = GetFileSignature(providedFilePath);
if (string.IsNullOrEmpty(sigAlgo))
{
unsignedFiles.Add(string.Format(unsignedFileStatusFormat, expectedAlgorithm, sigAlgo, providedFilePath));
}
else if (!sigAlgo.Equals(expectedAlgorithm, StringComparison.OrdinalIgnoreCase))
{
unsignedFiles.Add(string.Format(unsignedFileStatusFormat, expectedAlgorithm, sigAlgo, providedFilePath));
}
}
return unsignedFiles;
}
/// <summary>
/// Checks if a file is signed and returns the friendly alogrithm name of the file
/// Returns the friendly algorithm name of a file
/// </summary>
/// <param name="providedFilePath">Full file path for which Signature has to be verified</param>
/// <returns>Friendly Algorithm name, String.empty if not signed</returns>
private string GetFileSignature(string providedFilePath)
{
string friendlyAlgorithmName = string.Empty;
if (File.Exists(providedFilePath))
{
using (PowerShell ps = PowerShell.Create())
{
ps.AddCommand("Get-AuthenticodeSignature", true);
ps.AddParameter("FilePath", providedFilePath);
var cmdLetResults = ps.Invoke();
foreach (PSObject result in cmdLetResults)
{
Signature sig = (Signature)result.BaseObject;
if(sig.Status.Equals(SignatureStatus.Valid))
{
friendlyAlgorithmName = sig.SignerCertificate.SignatureAlgorithm.FriendlyName;
}
break;
}
}
}
return friendlyAlgorithmName;
}
#endregion
}
}