1 // Copyright (c) Microsoft. All rights reserved.
2 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 
4 using Microsoft.Build.Framework;
5 using Microsoft.Build.Utilities;
6 using Microsoft.Win32;
7 using System;
8 using System.Collections;
9 using System.Collections.Generic;
10 using System.ComponentModel;
11 using System.Deployment.Internal.CodeSigning;
12 using System.Diagnostics;
13 using System.Diagnostics.CodeAnalysis;
14 using System.Globalization;
15 using System.IO;
16 using System.Reflection;
17 using System.Runtime.InteropServices;
18 using System.Security;
19 using System.Security.Cryptography;
20 using System.Security.Cryptography.X509Certificates;
21 using System.Security.Permissions;
22 using System.Security.Policy;
23 using System.Text;
24 using System.Xml;
25 using FrameworkNameVersioning = System.Runtime.Versioning.FrameworkName;
26 
27 namespace Microsoft.Build.Tasks.Deployment.ManifestUtilities
28 {
29     /// <summary>
30     /// Provides a set of utility functions for manipulating security permision sets and signing.
31     /// </summary>
32     [ComVisible(false)]
33     public static class SecurityUtilities
34     {
35         private const string PermissionSetsFolder = "PermissionSets";
36         private const string Everything = "Everything";
37         private const string LocalIntranet = "LocalIntranet";
38         private const string Internet = "Internet";
39         private const string Custom = "Custom";
40         private const string ToolName = "signtool.exe";
41         private const int Fx2MajorVersion = 2;
42         private const int Fx3MajorVersion = 3;
43         private static readonly Version s_dotNet40Version = new Version("4.0");
44         private static readonly Version s_dotNet45Version = new Version("4.5");
45 
46         private const string InternetPermissionSetXml = "<PermissionSet class=\"System.Security.PermissionSet\" version=\"1\" ID=\"Custom\" SameSite=\"site\">\n" +
47                                                           "<IPermission class=\"System.Security.Permissions.FileDialogPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" version=\"1\" Access=\"Open\" />\n" +
48                                                           "<IPermission class=\"System.Security.Permissions.IsolatedStorageFilePermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" version=\"1\" Allowed=\"ApplicationIsolationByUser\" UserQuota=\"512000\" />\n" +
49                                                           "<IPermission class=\"System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" version=\"1\" Flags=\"Execution\" />\n" +
50                                                           "<IPermission class=\"System.Security.Permissions.UIPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" version=\"1\" Window=\"SafeTopLevelWindows\" Clipboard=\"OwnClipboard\" />\n" +
51                                                           "<IPermission class=\"System.Drawing.Printing.PrintingPermission, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\" version=\"1\" Level=\"SafePrinting\" />\n" +
52                                                         "</PermissionSet>";
53 
54         private const string LocalIntranetPermissionSetXml = "<PermissionSet class=\"System.Security.PermissionSet\" version=\"1\" ID=\"Custom\" SameSite=\"site\">\n" +
55                                                                   "<IPermission class=\"System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" version=\"1\" Read=\"USERNAME\" />\n" +
56                                                                   "<IPermission class=\"System.Security.Permissions.FileDialogPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" version=\"1\" Unrestricted=\"true\" />\n" +
57                                                                   "<IPermission class=\"System.Security.Permissions.IsolatedStorageFilePermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" version=\"1\" Allowed=\"AssemblyIsolationByUser\" UserQuota=\"9223372036854775807\" Expiry=\"9223372036854775807\" Permanent=\"True\" />\n" +
58                                                                   "<IPermission class=\"System.Security.Permissions.ReflectionPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" version=\"1\" Flags=\"ReflectionEmit\" />\n" +
59                                                                   "<IPermission class=\"System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" version=\"1\" Flags=\"Assertion, Execution, BindingRedirects\" />\n" +
60                                                                   "<IPermission class=\"System.Security.Permissions.UIPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" version=\"1\" Unrestricted=\"true\" />\n" +
61                                                                   "<IPermission class=\"System.Net.DnsPermission, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" version=\"1\" Unrestricted=\"true\" />\n" +
62                                                                   "<IPermission class=\"System.Drawing.Printing.PrintingPermission, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\" version=\"1\" Level=\"DefaultPrinting\" />\n" +
63                                                                 "</PermissionSet>";
64 
65         private const string InternetPermissionSetWithWPFXml = "<PermissionSet class=\"System.Security.PermissionSet\" version=\"1\" ID=\"Custom\" SameSite=\"site\">\n" +
66                                                                   "<IPermission class=\"System.Security.Permissions.FileDialogPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" version=\"1\" Access=\"Open\" />\n" +
67                                                                   "<IPermission class=\"System.Security.Permissions.IsolatedStorageFilePermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" version=\"1\" Allowed=\"ApplicationIsolationByUser\" UserQuota=\"512000\" />\n" +
68                                                                   "<IPermission class=\"System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" version=\"1\" Flags=\"Execution\" />\n" +
69                                                                   "<IPermission class=\"System.Security.Permissions.UIPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" version=\"1\" Window=\"SafeTopLevelWindows\" Clipboard=\"OwnClipboard\" />\n" +
70                                                                   "<IPermission class=\"System.Drawing.Printing.PrintingPermission, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\" version=\"1\" Level=\"SafePrinting\" />\n" +
71                                                                   "<IPermission class=\"System.Security.Permissions.MediaPermission, WindowsBase, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35\" version=\"1\" Audio=\"SafeAudio\" Video=\"SafeVideo\" Image=\"SafeImage\" />\n" +
72                                                                   "<IPermission class=\"System.Security.Permissions.WebBrowserPermission, WindowsBase, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35\" version=\"1\" Level=\"Safe\" />\n" +
73                                                                 "</PermissionSet>";
74 
75         private const string LocalIntranetPermissionSetWithWPFXml = "<PermissionSet class=\"System.Security.PermissionSet\" version=\"1\" ID=\"Custom\" SameSite=\"site\">\n" +
76                                                                           "<IPermission class=\"System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" version=\"1\" Read=\"USERNAME\" />\n" +
77                                                                           "<IPermission class=\"System.Security.Permissions.FileDialogPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" version=\"1\" Unrestricted=\"true\" />\n" +
78                                                                           "<IPermission class=\"System.Security.Permissions.IsolatedStorageFilePermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" version=\"1\" Allowed=\"AssemblyIsolationByUser\" UserQuota=\"9223372036854775807\" Expiry=\"9223372036854775807\" Permanent=\"True\" />\n" +
79                                                                           "<IPermission class=\"System.Security.Permissions.ReflectionPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" version=\"1\" Flags=\"ReflectionEmit\" />\n" +
80                                                                           "<IPermission class=\"System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" version=\"1\" Flags=\"Assertion, Execution, BindingRedirects\" />\n" +
81                                                                           "<IPermission class=\"System.Security.Permissions.UIPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" version=\"1\" Unrestricted=\"true\" />\n" +
82                                                                           "<IPermission class=\"System.Net.DnsPermission, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" version=\"1\" Unrestricted=\"true\" />\n" +
83                                                                           "<IPermission class=\"System.Drawing.Printing.PrintingPermission, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\" version=\"1\" Level=\"DefaultPrinting\" />\n" +
84                                                                           "<IPermission class=\"System.Security.Permissions.MediaPermission, WindowsBase, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35\" version=\"1\" Audio=\"SafeAudio\" Video=\"SafeVideo\" Image=\"SafeImage\" />\n" +
85                                                                           "<IPermission class=\"System.Security.Permissions.WebBrowserPermission, WindowsBase, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35\" version=\"1\" Level=\"Safe\" />\n" +
86                                                                         "</PermissionSet>";
87 
88         /// <summary>
89         /// Generates a permission set by computed the zone default permission set and adding any included permissions.
90         /// </summary>
91         /// <param name="targetZone">Specifies a zone default permission set, which is obtained from machine policy. Valid values are "Internet", "LocalIntranet", or "Custom". If "Custom" is specified, the generated permission set is based only on the includedPermissionSet parameter.</param>
92         /// <param name="includedPermissionSet">A PermissionSet object containing the set of permissions to be explicitly included in the generated permission set. Permissions specified in this parameter will be included verbatim in the generated permission set, regardless of targetZone parameter.</param>
93         /// <param name="excludedPermissions">This property is no longer used.</param>
94         /// <returns>The generated permission set.</returns>
ComputeZonePermissionSet(string targetZone, PermissionSet includedPermissionSet, string[] excludedPermissions)95         public static PermissionSet ComputeZonePermissionSet(string targetZone, PermissionSet includedPermissionSet, string[] excludedPermissions)
96         {
97             return ComputeZonePermissionSetHelper(targetZone, includedPermissionSet, null, string.Empty);
98         }
99 
ComputeZonePermissionSetHelper(string targetZone, PermissionSet includedPermissionSet, ITaskItem[] dependencies, string targetFrameworkMoniker)100         internal static PermissionSet ComputeZonePermissionSetHelper(string targetZone, PermissionSet includedPermissionSet, ITaskItem[] dependencies, string targetFrameworkMoniker)
101         {
102             // Custom Set.
103             if (String.IsNullOrEmpty(targetZone) || String.Equals(targetZone, Custom, StringComparison.OrdinalIgnoreCase))
104             {
105                 // just return the included set, no magic
106                 return includedPermissionSet.Copy();
107             }
108 
109             PermissionSet retSet = GetNamedPermissionSetFromZone(targetZone, dependencies, targetFrameworkMoniker);
110 
111             return retSet;
112         }
113 
GetNamedPermissionSetFromZone(string targetZone, ITaskItem[] dependencies, string targetFrameworkMoniker)114         private static PermissionSet GetNamedPermissionSetFromZone(string targetZone, ITaskItem[] dependencies, string targetFrameworkMoniker)
115         {
116             switch (targetZone)
117             {
118                 case LocalIntranet:
119                     return GetNamedPermissionSet(LocalIntranet, dependencies, targetFrameworkMoniker);
120                 case Internet:
121                     return GetNamedPermissionSet(Internet, dependencies, targetFrameworkMoniker);
122                 default:
123                     throw new ArgumentException(String.Empty /* no message */, "targetZone");
124             }
125         }
126 
GetNamedPermissionSet(string targetZone, ITaskItem[] dependencies, string targetFrameworkMoniker)127         private static PermissionSet GetNamedPermissionSet(string targetZone, ITaskItem[] dependencies, string targetFrameworkMoniker)
128         {
129             FrameworkNameVersioning fn = null;
130 
131             if (!string.IsNullOrEmpty(targetFrameworkMoniker))
132             {
133                 fn = new FrameworkNameVersioning(targetFrameworkMoniker);
134             }
135             else
136             {
137                 fn = new FrameworkNameVersioning(".NETFramework", s_dotNet40Version);
138             }
139 
140             int majorVersion = fn.Version.Major;
141 
142             if (majorVersion == Fx2MajorVersion)
143             {
144                 return SecurityUtilities.XmlToPermissionSet((GetXmlElement(targetZone, majorVersion)));
145             }
146             else if (majorVersion == Fx3MajorVersion)
147             {
148                 return SecurityUtilities.XmlToPermissionSet((GetXmlElement(targetZone, majorVersion)));
149             }
150             else
151             {
152                 return SecurityUtilities.XmlToPermissionSet((GetXmlElement(targetZone, fn)));
153             }
154         }
155 
GetXmlElement(string targetZone, FrameworkNameVersioning fn)156         private static XmlElement GetXmlElement(string targetZone, FrameworkNameVersioning fn)
157         {
158             IList<string> paths = ToolLocationHelper.GetPathToReferenceAssemblies(fn);
159 
160             // Is the targeted CLR even installed?
161             if (paths.Count > 0)
162             {
163                 // first one is always framework requested.
164                 string path = Path.Combine(paths[0], PermissionSetsFolder);
165 
166                 // PermissionSets folder doesn't exit
167                 if (Directory.Exists(path))
168                 {
169                     string[] files = Directory.GetFiles(path, "*.xml");
170                     FileInfo[] filesInfo = new FileInfo[files.Length];
171 
172                     int indexFound = -1;
173 
174                     // trim the extension.
175                     for (int i = 0; i < files.Length; i++)
176                     {
177                         filesInfo[i] = new FileInfo(files[i]);
178 
179                         string fileInfoNoExt = Path.GetFileNameWithoutExtension(files[i]);
180 
181                         if (string.Equals(fileInfoNoExt, targetZone, StringComparison.OrdinalIgnoreCase))
182                         {
183                             indexFound = i;
184                             break;
185                         }
186                     }
187 
188                     if (indexFound != -1)
189                     {
190                         string data = string.Empty;
191                         FileInfo resultFile = filesInfo[indexFound];
192                         using (FileStream fs = resultFile.OpenRead())
193                         {
194                             try
195                             {
196                                 StreamReader sr = new StreamReader(fs);
197                                 data = sr.ReadToEnd(); // fs.Position value will be the length of the stream.
198                                 if (!string.IsNullOrEmpty(data))
199                                 {
200                                     XmlDocument doc = new XmlDocument();
201                                     XmlReaderSettings xrSettings = new XmlReaderSettings();
202                                     xrSettings.DtdProcessing = DtdProcessing.Ignore;
203 
204                                     // http://msdn.microsoft.com/en-us/library/h2344bs2(v=vs.110).aspx
205                                     // PermissionSets do not conform to document level, which is the default setting.
206                                     xrSettings.ConformanceLevel = ConformanceLevel.Auto;
207                                     try
208                                     {
209                                         fs.Position = 0; // Reset to 0 before using this stream in any other reader.
210                                         using (XmlReader xr = XmlReader.Create(fs, xrSettings))
211                                         {
212                                             doc.Load(xr);
213                                             return (XmlElement)doc.DocumentElement;
214                                         }
215                                     }
216                                     catch (Exception)
217                                     {
218                                         //continue.
219                                     }
220                                 }
221                             }
222                             catch (ArgumentException)
223                             {
224                                 //continue.
225                             }
226                         }
227                     }
228                 }
229             }
230 
231 #if !MONO
232             return GetCurrentCLRPermissions(targetZone);
233 #else
234             throw new NotImplementedException();
235 #endif
236         }
237 
238 #if !MONO
239         [SuppressMessage("Microsoft.Security.Xml", "CA3057: DoNotUseLoadXml.")]
GetCurrentCLRPermissions(string targetZone)240         private static XmlElement GetCurrentCLRPermissions(string targetZone)
241         {
242             string resultInString = string.Empty;
243             SecurityZone zone = SecurityZone.NoZone;
244             switch (targetZone)
245             {
246                 case LocalIntranet:
247                     zone = SecurityZone.Intranet;
248                     break;
249                 case Internet:
250                     zone = SecurityZone.Internet;
251                     break;
252                 default:
253                     throw new ArgumentException(String.Empty /* no message */, "targetZone");
254             }
255 
256             Evidence evidence = new Evidence(new EvidenceBase[] { new Zone(zone), new System.Runtime.Hosting.ActivationArguments(new System.ApplicationIdentity("")) }, null);
257 
258             PermissionSet sandbox = SecurityManager.GetStandardSandbox(evidence);
259             resultInString = sandbox.ToString();
260 
261             if (!string.IsNullOrEmpty(resultInString))
262             {
263                 XmlDocument doc = new XmlDocument();
264                 // CA3057: DoNotUseLoadXml. Suppressed since the xml being loaded is a string representation of the PermissionSet.
265                 doc.LoadXml(resultInString);
266 
267                 return (XmlElement)doc.DocumentElement;
268             }
269 
270             return null;
271         }
272 #endif
273 
GetXmlElement(string targetZone, int majorVersion)274         private static XmlElement GetXmlElement(string targetZone, int majorVersion)
275         {
276             XmlDocument doc = null;
277 
278             switch (majorVersion)
279             {
280                 case Fx2MajorVersion:
281                     doc = CreateXmlDocV2(targetZone);
282                     break;
283                 case Fx3MajorVersion:
284                     doc = CreateXmlDocV3(targetZone);
285                     break;
286                 default:
287                     throw new ArgumentException(String.Empty /* no message */, "majorVersion");
288             }
289 
290             XmlElement rootElement = (XmlElement)doc.DocumentElement;
291 
292             if (rootElement == null)
293                 return null;
294 
295             return rootElement;
296         }
297 
298         [SuppressMessage("Microsoft.Security.Xml", "CA3057: DoNotUseLoadXml.")]
CreateXmlDocV2(string targetZone)299         private static XmlDocument CreateXmlDocV2(string targetZone)
300         {
301             XmlDocument doc = new XmlDocument();
302 
303             switch (targetZone)
304             {
305                 case LocalIntranet:
306                     // CA3057: DoNotUseLoadXml.  Suppressed since is LocalIntranetPermissionSetXml a constant string.
307                     doc.LoadXml(LocalIntranetPermissionSetXml);
308                     return doc;
309                 case Internet:
310                     // CA3057: DoNotUseLoadXml.  Suppressed since is InternetPermissionSetXml a constant string.
311                     doc.LoadXml(InternetPermissionSetXml);
312                     return doc;
313                 default:
314                     throw new ArgumentException(String.Empty /* no message */, "targetZone");
315             }
316         }
317 
318         [SuppressMessage("Microsoft.Security.Xml", "CA3057: DoNotUseLoadXml.")]
CreateXmlDocV3(string targetZone)319         private static XmlDocument CreateXmlDocV3(string targetZone)
320         {
321             XmlDocument doc = new XmlDocument();
322 
323             switch (targetZone)
324             {
325                 case LocalIntranet:
326                     // CA3057: DoNotUseLoadXml.  Suppressed since is LocalIntranetPermissionSetXml a constant string.
327                     doc.LoadXml(LocalIntranetPermissionSetWithWPFXml);
328                     return doc;
329                 case Internet:
330                     // CA3057: DoNotUseLoadXml.  Suppressed since is InternetPermissionSetXml a constant string.
331                     doc.LoadXml(InternetPermissionSetWithWPFXml);
332                     return doc;
333                 default:
334                     throw new ArgumentException(String.Empty /* no message */, "targetZone");
335             }
336         }
337 
GetRegistryPermissionSetByName(string name)338         private static string[] GetRegistryPermissionSetByName(string name)
339         {
340             string[] extensibleNamedPermissionSetRegistryInfo = null;
341             RegistryKey localMachineKey = Registry.LocalMachine;
342 
343             using (RegistryKey versionIndependentFXKey = localMachineKey.OpenSubKey(@"Software\Microsoft\.NETFramework", false))
344             {
345                 if (versionIndependentFXKey != null)
346                 {
347                     using (RegistryKey namedPermissionSetsKey = versionIndependentFXKey.OpenSubKey(@"Security\Policy\Extensions\NamedPermissionSets", false))
348                     {
349                         if (namedPermissionSetsKey != null)
350                         {
351                             using (RegistryKey permissionSetKey = namedPermissionSetsKey.OpenSubKey(name, false))
352                             {
353                                 if (permissionSetKey != null)
354                                 {
355                                     string[] permissionKeys = permissionSetKey.GetSubKeyNames();
356                                     extensibleNamedPermissionSetRegistryInfo = new string[permissionKeys.Length];
357                                     for (int i = 0; i < permissionKeys.Length; i++)
358                                     {
359                                         using (RegistryKey permissionKey = permissionSetKey.OpenSubKey(permissionKeys[i], false))
360                                         {
361                                             string permissionXml = permissionKey.GetValue("Xml") as string;
362                                             extensibleNamedPermissionSetRegistryInfo[i] = permissionXml;
363                                         }
364                                     }
365                                 }
366                             }
367                         }
368                     }
369                 }
370             }
371             return extensibleNamedPermissionSetRegistryInfo;
372         }
373 
374 #if !MONO
RemoveNonReferencedPermissions(string[] setToFilter, ITaskItem[] dependencies)375         private static PermissionSet RemoveNonReferencedPermissions(string[] setToFilter, ITaskItem[] dependencies)
376         {
377             PermissionSet retSet = new PermissionSet(PermissionState.None);
378             if (dependencies == null || setToFilter == null || setToFilter.Length == 0)
379                 return retSet;
380 
381             List<string> assemblyNameList = new List<string>();
382             foreach (ITaskItem dependency in dependencies)
383             {
384                 AssemblyName dependentAssemblyName = AssemblyName.GetAssemblyName(dependency.ItemSpec);
385                 assemblyNameList.Add(dependentAssemblyName.Name + ", " + dependentAssemblyName.Version.ToString());
386             }
387             SecurityElement retSetElement = retSet.ToXml();
388             foreach (string permissionXml in setToFilter)
389             {
390                 if (!String.IsNullOrEmpty(permissionXml))
391                 {
392                     string permissionAssemblyName;
393                     string className;
394                     string assemblyVersion;
395 
396                     SecurityElement permission = SecurityElement.FromString(permissionXml);
397 
398                     if (!ParseElementForAssemblyIdentification(permission, out className, out permissionAssemblyName, out assemblyVersion))
399                         continue;
400                     if (assemblyNameList.Contains(permissionAssemblyName + ", " + assemblyVersion))
401                     {
402                         retSetElement.AddChild(SecurityElement.FromString(permissionXml));
403                     }
404                 }
405             }
406             retSet = new ReadOnlyPermissionSet(retSetElement);
407             return retSet;
408         }
409 #endif
410 
ParseElementForAssemblyIdentification(SecurityElement el, out String className, out String assemblyName, out String assemblyVersion)411         internal static bool ParseElementForAssemblyIdentification(SecurityElement el,
412                                                                    out String className,
413                                                                    out String assemblyName, // for example "WindowsBase"
414                                                                    out String assemblyVersion)
415         {
416             className = null;
417             assemblyName = null;
418             assemblyVersion = null;
419 
420             String fullClassName = el.Attribute("class");
421 
422             if (fullClassName == null)
423             {
424                 return false;
425             }
426             if (fullClassName.IndexOf('\'') >= 0)
427             {
428                 fullClassName = fullClassName.Replace('\'', '\"');
429             }
430 
431             int commaIndex = fullClassName.IndexOf(',');
432             int namespaceClassNameLength;
433 
434             // If the classname is tagged with assembly information, find where
435             // the assembly information begins.
436 
437             if (commaIndex == -1)
438             {
439                 return false;
440             }
441 
442             namespaceClassNameLength = commaIndex;
443             className = fullClassName.Substring(0, namespaceClassNameLength);
444             String assemblyFullName = fullClassName.Substring(commaIndex + 1);
445             AssemblyName an = new AssemblyName(assemblyFullName);
446             assemblyName = an.Name;
447             assemblyVersion = an.Version.ToString();
448             return true;
449         }
450 
451 
452         /// <summary>
453         /// Converts an array of permission identity strings to a permission set object.
454         /// </summary>
455         /// <param name="ids">An array of permission identity strings.</param>
456         /// <returns>The converted permission set.</returns>
IdentityListToPermissionSet(string[] ids)457         public static PermissionSet IdentityListToPermissionSet(string[] ids)
458         {
459             XmlDocument document = new XmlDocument();
460             XmlElement permissionSetElement = document.CreateElement("PermissionSet");
461             document.AppendChild(permissionSetElement);
462             foreach (string id in ids)
463             {
464                 XmlElement permissionElement = document.CreateElement("IPermission");
465                 XmlAttribute a = document.CreateAttribute("class");
466                 a.Value = id;
467                 permissionElement.Attributes.Append(a);
468                 permissionSetElement.AppendChild(permissionElement);
469             }
470             return XmlToPermissionSet(permissionSetElement);
471         }
472 
473         /// <summary>
474         /// Converts a permission set object to an array of permission identity strings.
475         /// </summary>
476         /// <param name="permissionSet">The input permission set to be converted.</param>
477         /// <returns>An array of permission identity strings.</returns>
478         [SuppressMessage("Microsoft.Security.Xml", "CA3057: DoNotUseLoadXml.")]
PermissionSetToIdentityList(PermissionSet permissionSet)479         public static string[] PermissionSetToIdentityList(PermissionSet permissionSet)
480         {
481             string psXml = permissionSet != null ? permissionSet.ToString() : "<PermissionSet/>";
482             XmlDocument psDocument = new XmlDocument();
483             // CA3057: DoNotUseLoadXml.  Suppressed since 'psXml' is a trusted or a constant string.
484             psDocument.LoadXml(psXml);
485             return XmlToIdentityList(psDocument.DocumentElement);
486         }
487 
488         [SuppressMessage("Microsoft.Security.Xml", "CA3057: DoNotUseLoadXml.")]
PermissionSetToXml(PermissionSet ps)489         internal static XmlDocument PermissionSetToXml(PermissionSet ps)
490         {
491             XmlDocument inputDocument = new XmlDocument();
492             string xml = (ps != null) ? ps.ToString() : "<PermissionSet/>";
493 
494             // CA3057: DoNotUseLoadXml.  Suppressed since 'xml' is a trusted or a constant string.
495             inputDocument.LoadXml(xml);
496             XmlDocument outputDocument = new XmlDocument();
497             XmlElement psElement = XmlUtil.CloneElementToDocument(inputDocument.DocumentElement, outputDocument, XmlNamespaces.asmv2);
498             outputDocument.AppendChild(psElement);
499             return outputDocument;
500         }
501 
XmlElementToSecurityElement(XmlElement xe)502         private static SecurityElement XmlElementToSecurityElement(XmlElement xe)
503         {
504             SecurityElement se = new SecurityElement(xe.Name);
505             foreach (XmlAttribute xa in xe.Attributes)
506                 se.AddAttribute(xa.Name, xa.Value);
507             foreach (XmlNode xn in xe.ChildNodes)
508                 if (xn.NodeType == XmlNodeType.Element)
509                     se.AddChild(XmlElementToSecurityElement((XmlElement)xn));
510             return se;
511         }
512 
XmlToIdentityList(XmlElement psElement)513         private static string[] XmlToIdentityList(XmlElement psElement)
514         {
515             XmlNamespaceManager nsmgr = XmlNamespaces.GetNamespaceManager(psElement.OwnerDocument.NameTable);
516             XmlNodeList nodes = psElement.SelectNodes(XPaths.permissionClassAttributeQuery, nsmgr);
517             if (nodes == null || nodes.Count == 0)
518                 nodes = psElement.SelectNodes(XmlUtil.TrimPrefix(XPaths.permissionClassAttributeQuery));
519             string[] a;
520             if (nodes != null)
521             {
522                 a = new string[nodes.Count];
523                 int i = 0;
524                 foreach (XmlNode node in nodes)
525                     a[i++] = node.Value;
526             }
527             else
528                 a = Array.Empty<string>();
529             return a;
530         }
531 
532         /// <summary>
533         /// Converts an XmlElement into a PermissionSet object.
534         /// </summary>
535         /// <param name="element">An XML representation of the permission set.</param>
536         /// <returns>The converted permission set.</returns>
XmlToPermissionSet(XmlElement element)537         public static PermissionSet XmlToPermissionSet(XmlElement element)
538         {
539 #if !MONO
540             if (element == null)
541                 return null;
542 
543             SecurityElement se = XmlElementToSecurityElement(element);
544             if (se == null)
545                 return null;
546 
547             PermissionSet ps = new PermissionSet(PermissionState.None);
548             try
549             {
550                 ps = new ReadOnlyPermissionSet(se);
551             }
552             catch (ArgumentException ex)
553             {
554                 //UNDONE: Need to log exception thrown from PermissionSet.FromXml
555                 Debug.Fail(String.Format(CultureInfo.CurrentCulture, "PermissionSet.FromXml failed: {0}\r\n\r\n{1}", ex.Message, element.OuterXml));
556                 return null;
557             }
558             return ps;
559 #else
560             throw new NotImplementedException();
561 #endif
562         }
563 
564         /// <summary>
565         /// Signs a ClickOnce manifest or PE file.
566         /// </summary>
567         /// <param name="certThumbprint">Hexadecimal string that contains the SHA-1 hash of the certificate.</param>
568         /// <param name="timestampUrl">URL that specifies an address of a time stamping server.</param>
569         /// <param name="path">Path of the file to sign with the certificate.</param>
SignFile(string certThumbprint, Uri timestampUrl, string path)570         public static void SignFile(string certThumbprint, Uri timestampUrl, string path)
571         {
572             SignFile(certThumbprint, timestampUrl, path, null);
573         }
574 
575         /// <summary>
576         /// Signs a ClickOnce manifest or PE file.
577         /// </summary>
578         /// <param name="certThumbprint">Hexadecimal string that contains the SHA-1 hash of the certificate.</param>
579         /// <param name="timestampUrl">URL that specifies an address of a time stamping server.</param>
580         /// <param name="path">Path of the file to sign with the certificate.</param>
581         /// <param name="targetFrameworkVersion">Version of the .NET Framework for the target.</param>
SignFile(string certThumbprint, Uri timestampUrl, string path, string targetFrameworkVersion)582         public static void SignFile(string certThumbprint, Uri timestampUrl, string path, string targetFrameworkVersion)
583         {
584             System.Resources.ResourceManager resources = new System.Resources.ResourceManager("Microsoft.Build.Tasks.Core.Strings.ManifestUtilities", typeof(SecurityUtilities).Module.Assembly);
585 
586             if (String.IsNullOrEmpty(certThumbprint))
587                 throw new ArgumentNullException("certThumbprint");
588 
589             X509Certificate2 cert = GetCert(certThumbprint);
590 
591             if (cert == null)
592                 throw new ArgumentException(resources.GetString("CertNotInStore"), "certThumbprint");
593 
594             if (!String.IsNullOrEmpty(targetFrameworkVersion))
595             {
596                 Version targetVersion = Util.GetTargetFrameworkVersion(targetFrameworkVersion);
597 
598                 if (targetVersion == null)
599                     throw new ArgumentException("TargetFrameworkVersion");
600 
601                 // SHA-256 digest can be parsed only with .NET 4.5 or higher.
602                 bool isTargetFrameworkSha256Supported = targetVersion.CompareTo(s_dotNet45Version) >= 0;
603                 SignFileInternal(cert, timestampUrl, path, isTargetFrameworkSha256Supported, resources);
604             }
605             else
606             {
607                 SignFile(cert, timestampUrl, path);
608             }
609         }
610 
611         // We need to refactor these functions to handle real sign tool
612         /// <summary>
613         /// Signs a ClickOnce manifest.
614         /// </summary>
615         /// <param name="certPath">The certificate to be used to sign the file.</param>
616         /// <param name="certPassword">The certificate password.</param>
617         /// <param name="timestampUrl">URL that specifies an address of a time stamping server.</param>
618         /// <param name="path">Path of the file to sign with the certificate.</param>
619         /// <remarks>This function is only for signing a manifest, not a PE file.</remarks>
SignFile(string certPath, SecureString certPassword, Uri timestampUrl, string path)620         public static void SignFile(string certPath, SecureString certPassword, Uri timestampUrl, string path)
621         {
622             X509Certificate2 cert = new X509Certificate2(certPath, certPassword, X509KeyStorageFlags.PersistKeySet);
623             SignFile(cert, timestampUrl, path);
624         }
625 
UseSha256Algorithm(X509Certificate2 cert)626         private static bool UseSha256Algorithm(X509Certificate2 cert)
627         {
628             Oid oid = cert.SignatureAlgorithm;
629             return string.Equals(oid.FriendlyName, "sha256RSA", StringComparison.OrdinalIgnoreCase);
630         }
631 
632         /// <summary>
633         /// Signs a ClickOnce manifest or PE file.
634         /// </summary>
635         /// <param name="cert">The certificate to be used to sign the file.</param>
636         /// <param name="timestampUrl">URL that specifies an address of a time stamping server.</param>
637         /// <param name="path">Path of the file to sign with the certificate.</param>
638         /// <remarks>This function can only sign a PE file if the X509Certificate2 parameter represents a certificate in the
639         /// current user's personal certificate store.</remarks>
SignFile(X509Certificate2 cert, Uri timestampUrl, string path)640         public static void SignFile(X509Certificate2 cert, Uri timestampUrl, string path)
641         {
642             // setup resources
643             System.Resources.ResourceManager resources = new System.Resources.ResourceManager("Microsoft.Build.Tasks.Core.Strings.ManifestUtilities", typeof(SecurityUtilities).Module.Assembly);
644             SignFileInternal(cert, timestampUrl, path, true, resources);
645         }
646 
SignFileInternal(X509Certificate2 cert, Uri timestampUrl, string path, bool targetFrameworkSupportsSha256, System.Resources.ResourceManager resources)647         private static void SignFileInternal(X509Certificate2 cert, Uri timestampUrl, string path, bool targetFrameworkSupportsSha256, System.Resources.ResourceManager resources)
648         {
649             if (cert == null)
650                 throw new ArgumentNullException("cert");
651 
652             if (String.IsNullOrEmpty(path))
653                 throw new ArgumentNullException("path");
654 
655             if (!File.Exists(path))
656                 throw new FileNotFoundException(String.Format(CultureInfo.InvariantCulture, resources.GetString("SecurityUtil.SignTargetNotFound"), path), path);
657 
658             bool useSha256 = UseSha256Algorithm(cert) && targetFrameworkSupportsSha256;
659 
660             if (PathUtil.IsPEFile(path))
661             {
662                 if (IsCertInStore(cert))
663                     SignPEFile(cert, timestampUrl, path, resources, useSha256);
664                 else
665                     throw new InvalidOperationException(resources.GetString("SignFile.CertNotInStore"));
666             }
667             else
668             {
669                 using(RSA rsa = CngLightup.GetRSAPrivateKey(cert))
670                 {
671                     if (rsa == null)
672                         throw new ApplicationException(resources.GetString("SecurityUtil.OnlyRSACertsAreAllowed"));
673                     try
674                     {
675                         XmlDocument doc = new XmlDocument();
676                         doc.PreserveWhitespace = true;
677                         XmlReaderSettings xrSettings = new XmlReaderSettings();
678                         xrSettings.DtdProcessing = DtdProcessing.Ignore;
679                         using (XmlReader xr = XmlReader.Create(path, xrSettings))
680                         {
681                             doc.Load(xr);
682                         }
683                         SignedCmiManifest2 manifest = new SignedCmiManifest2(doc, useSha256);
684                         CmiManifestSigner2 signer;
685                         if (useSha256 && rsa is RSACryptoServiceProvider)
686                         {
687                             RSACryptoServiceProvider csp = SignedCmiManifest2.GetFixedRSACryptoServiceProvider(rsa as RSACryptoServiceProvider, useSha256);
688                             signer = new CmiManifestSigner2(csp, cert, useSha256);
689                         }
690                         else
691                         {
692                             signer = new CmiManifestSigner2(rsa, cert, useSha256);
693                         }
694 
695                         if (timestampUrl == null)
696                             manifest.Sign(signer);
697                         else
698                             manifest.Sign(signer, timestampUrl.ToString());
699                         doc.Save(path);
700                     }
701                     catch (Exception ex)
702                     {
703                         int exceptionHR = System.Runtime.InteropServices.Marshal.GetHRForException(ex);
704                         if (exceptionHR == -2147012889 || exceptionHR == -2147012867)
705                         {
706                             throw new ApplicationException(resources.GetString("SecurityUtil.TimestampUrlNotFound"), ex);
707                         }
708                         throw new ApplicationException(ex.Message, ex);
709                     }
710                 }
711             }
712         }
713 
714 
SignPEFile(X509Certificate2 cert, System.Uri timestampUrl, string path, System.Resources.ResourceManager resources, bool useSha256)715         private static void SignPEFile(X509Certificate2 cert, System.Uri timestampUrl, string path, System.Resources.ResourceManager resources, bool useSha256)
716         {
717             ProcessStartInfo startInfo = new ProcessStartInfo(GetPathToTool(resources), GetCommandLineParameters(cert.Thumbprint, timestampUrl, path, useSha256));
718             startInfo.CreateNoWindow = true;
719             startInfo.UseShellExecute = false;
720             startInfo.RedirectStandardError = true;
721             startInfo.RedirectStandardOutput = true;
722 
723             Process signTool = null;
724 
725             try
726             {
727                 signTool = Process.Start(startInfo);
728                 signTool.WaitForExit();
729 
730                 while (!signTool.HasExited)
731                 {
732                     System.Threading.Thread.Sleep(50);
733                 }
734                 switch (signTool.ExitCode)
735                 {
736                     case 0:
737                         // everything was fine
738                         break;
739                     case 1:
740                         // error, report it
741                         throw new ApplicationException(String.Format(CultureInfo.InvariantCulture, resources.GetString("SecurityUtil.SigntoolFail"), path, signTool.StandardError.ReadToEnd()));
742                     case 2:
743                         // warning, report it
744                         throw new WarningException(String.Format(CultureInfo.InvariantCulture, resources.GetString("SecurityUtil.SigntoolWarning"), path, signTool.StandardError.ReadToEnd()));
745                     default:
746                         // treat as error
747                         throw new ApplicationException(String.Format(CultureInfo.InvariantCulture, resources.GetString("SecurityUtil.SigntoolFail"), path, signTool.StandardError.ReadToEnd()));
748                 }
749             }
750             finally
751             {
752                 if (signTool != null)
753                     signTool.Close();
754             }
755         }
756 
GetCommandLineParameters(string certThumbprint, Uri timestampUrl, string path, bool useSha256)757         internal static string GetCommandLineParameters(string certThumbprint, Uri timestampUrl, string path, bool useSha256)
758         {
759             StringBuilder commandLine = new StringBuilder();
760             if (useSha256)
761                 commandLine.Append(String.Format(CultureInfo.InvariantCulture, "sign /fd sha256 /sha1 {0} ", certThumbprint));
762             else
763             {
764                 commandLine.Append(String.Format(CultureInfo.InvariantCulture, "sign /sha1 {0} ", certThumbprint));
765             }
766             if (timestampUrl != null)
767                 commandLine.Append(String.Format(CultureInfo.InvariantCulture, "/t {0} ", timestampUrl.ToString()));
768             commandLine.Append(string.Format(CultureInfo.InvariantCulture, "\"{0}\"", path));
769             return commandLine.ToString();
770         }
771 
GetPathToTool(System.Resources.ResourceManager resources)772         internal static string GetPathToTool(System.Resources.ResourceManager resources)
773         {
774 #pragma warning disable 618 // Disabling warning on using internal ToolLocationHelper API. At some point we should migrate this.
775             string toolPath = ToolLocationHelper.GetPathToWindowsSdkFile(ToolName, TargetDotNetFrameworkVersion.VersionLatest, VisualStudioVersion.VersionLatest);
776             if (toolPath == null || !File.Exists(toolPath))
777             {
778                 toolPath = ToolLocationHelper.GetPathToWindowsSdkFile(ToolName, TargetDotNetFrameworkVersion.Version45,
779                     VisualStudioVersion.Version110);
780             }
781             if (toolPath == null || !File.Exists(toolPath))
782             {
783                 var pathToDotNetFrameworkSdk = ToolLocationHelper.GetPathToDotNetFrameworkSdk(TargetDotNetFrameworkVersion.Version40, VisualStudioVersion.Version100);
784                 if (pathToDotNetFrameworkSdk != null)
785                 {
786                     toolPath = Path.Combine(pathToDotNetFrameworkSdk, "bin", ToolName);
787                 }
788             }
789             if (toolPath == null || !File.Exists(toolPath))
790             {
791                 toolPath = GetVersionIndependentToolPath(ToolName);
792             }
793             if (toolPath == null || !File.Exists(toolPath))
794             {
795                 toolPath = Path.Combine(Directory.GetCurrentDirectory(), ToolName);
796             }
797             if (!File.Exists(toolPath))
798             {
799                 throw new ApplicationException(String.Format(CultureInfo.CurrentCulture,
800                     resources.GetString("SecurityUtil.SigntoolNotFound"), toolPath));
801             }
802 
803             return toolPath;
804 #pragma warning restore 618
805         }
806 
GetCert(string thumbprint)807         internal static X509Certificate2 GetCert(string thumbprint)
808         {
809             X509Store personalStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
810             try
811             {
812                 personalStore.Open(OpenFlags.ReadOnly);
813                 X509Certificate2Collection foundCerts = personalStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);
814                 if (foundCerts.Count == 1)
815                 {
816                     return foundCerts[0];
817                 }
818             }
819             finally
820             {
821                 personalStore.Close();
822             }
823             return null;
824         }
825 
IsCertInStore(X509Certificate2 cert)826         private static bool IsCertInStore(X509Certificate2 cert)
827         {
828             X509Store personalStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
829             try
830             {
831                 personalStore.Open(OpenFlags.ReadOnly);
832                 X509Certificate2Collection foundCerts = personalStore.Certificates.Find(X509FindType.FindByThumbprint, cert.Thumbprint, false);
833                 if (foundCerts.Count == 1)
834                     return true;
835             }
836             finally
837             {
838                 personalStore.Close();
839             }
840             return false;
841         }
842 
GetVersionIndependentToolPath(string toolName)843         private static string GetVersionIndependentToolPath(string toolName)
844         {
845             RegistryKey localMachineKey = Registry.LocalMachine;
846             const string versionIndependentToolKeyName = @"Software\Microsoft\ClickOnce\SignTool";
847 
848             using (RegistryKey versionIndependentToolKey = localMachineKey.OpenSubKey(versionIndependentToolKeyName, writable: false))
849             {
850                 string versionIndependentToolPath = null;
851 
852                 if (versionIndependentToolKey != null)
853                 {
854                     versionIndependentToolPath = versionIndependentToolKey.GetValue("Path") as string;
855                 }
856 
857                 return versionIndependentToolPath != null ? Path.Combine(versionIndependentToolPath, toolName) : null;
858             }
859         }
860     }
861 }
862