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 System;
5 using System.Diagnostics;
6 using System.IO;
7 using System.Reflection;
8 using System.Security;
9 using System.Runtime.InteropServices;
10 
11 using Microsoft.Build.Shared;
12 using Microsoft.Build.Utilities;
13 
14 namespace Microsoft.Build.Tasks
15 {
16     /// <summary>
17     /// Possible strong name states of an assembly
18     /// </summary>
19     internal enum StrongNameLevel
20     {
21         None, DelaySigned, FullySigned, Unknown
22     };
23 
24     /// <summary>
25     /// Strong naming utilities.
26     /// </summary>
27     internal static class StrongNameUtils
28     {
29         /// <summary>
30         /// Reads contents of a key file. Reused from vsdesigner code.
31         /// </summary>
32         /// <param name="log"></param>
33         /// <param name="keyFile"></param>
34         /// <param name="keyPair"></param>
35         /// <param name="publicKey"></param>
ReadKeyFile(TaskLoggingHelper log, string keyFile, out StrongNameKeyPair keyPair, out byte[] publicKey)36         internal static void ReadKeyFile(TaskLoggingHelper log, string keyFile, out StrongNameKeyPair keyPair, out byte[] publicKey)
37         {
38             // Initialize parameters
39             keyPair = null;
40             publicKey = null;
41 
42             byte[] keyFileContents;
43 
44             try
45             {
46                 // Read the stuff from the file stream
47                 using (FileStream fs = new FileStream(keyFile, FileMode.Open, FileAccess.Read, FileShare.Read))
48                 {
49                     keyFileContents = new byte[(int)fs.Length];
50                     fs.Read(keyFileContents, 0, (int)fs.Length);
51                 }
52             }
53             catch (ArgumentException e)
54             {
55                 log.LogErrorWithCodeFromResources("StrongNameUtils.KeyFileReadFailure", keyFile);
56                 log.LogErrorFromException(e);
57                 throw new StrongNameException(e);
58             }
59             catch (IOException e)
60             {
61                 log.LogErrorWithCodeFromResources("StrongNameUtils.KeyFileReadFailure", keyFile);
62                 log.LogErrorFromException(e);
63                 throw new StrongNameException(e);
64             }
65             catch (SecurityException e)
66             {
67                 log.LogErrorWithCodeFromResources("StrongNameUtils.KeyFileReadFailure", keyFile);
68                 log.LogErrorFromException(e);
69                 throw new StrongNameException(e);
70             }
71 
72             // Make a new key pair from what we read
73             StrongNameKeyPair snp = new StrongNameKeyPair(keyFileContents);
74 
75             // If anything fails reading the public key portion of the strong name key pair, then
76             // assume that keyFile contained only the public key portion of the public/private pair.
77             try
78             {
79                 publicKey = snp.PublicKey;
80 
81                 // If we didn't throw up to this point then we have a valid public/private key pair,
82                 // so assign the object just created above to the out parameter.
83                 keyPair = snp;
84             }
85             catch (ArgumentException)
86             {
87                 publicKey = keyFileContents;
88             }
89         }
90 
91         /// <summary>
92         /// Given a key file or container, extract private/public key data. Reused from vsdesigner code.
93         /// </summary>
94         /// <param name="log"></param>
95         /// <param name="keyFile"></param>
96         /// <param name="keyContainer"></param>
97         /// <param name="keyPair"></param>
98         /// <param name="publicKey"></param>
GetStrongNameKey(TaskLoggingHelper log, string keyFile, string keyContainer, out StrongNameKeyPair keyPair, out byte[] publicKey)99         internal static void GetStrongNameKey(TaskLoggingHelper log, string keyFile, string keyContainer, out StrongNameKeyPair keyPair, out byte[] publicKey)
100         {
101             // Gets either a strong name key pair from the key file or a key container.
102             // If keyFile and keyContainer are both null/zero length then returns null.
103             // Initialize parameters
104             keyPair = null;
105             publicKey = null;
106             if (keyContainer != null && keyContainer.Length != 0)
107             {
108                 try
109                 {
110                     keyPair = new StrongNameKeyPair(keyContainer);
111                     publicKey = keyPair.PublicKey;
112                 }
113                 catch (SecurityException e)
114                 {
115                     log.LogErrorWithCodeFromResources("StrongNameUtils.BadKeyContainer", keyContainer);
116                     log.LogErrorFromException(e);
117                     throw new StrongNameException(e);
118                 }
119                 catch (ArgumentException e)
120                 {
121                     log.LogErrorWithCodeFromResources("StrongNameUtils.BadKeyContainer", keyContainer);
122                     log.LogErrorFromException(e);
123                     throw new StrongNameException(e);
124                 }
125             }
126             else if (keyFile != null && keyFile.Length != 0)
127             {
128                 ReadKeyFile(log, keyFile, out keyPair, out publicKey);
129             }
130         }
131 
132         /// <summary>
133         /// Given an assembly path, determine if the assembly is [delay] signed or not. This code is based on similar unmanaged
134         /// routines in vsproject and sn.exe (ndp tools) codebases.
135         /// </summary>
136         /// <param name="assemblyPath"></param>
137         /// <returns></returns>
GetAssemblyStrongNameLevel(string assemblyPath)138         internal static StrongNameLevel GetAssemblyStrongNameLevel(string assemblyPath)
139         {
140             ErrorUtilities.VerifyThrowArgumentNull(assemblyPath, "assemblyPath");
141 
142             StrongNameLevel snLevel = StrongNameLevel.Unknown;
143             IntPtr fileHandle = NativeMethods.InvalidIntPtr;
144 
145             try
146             {
147                 // open the assembly
148                 fileHandle = NativeMethods.CreateFile(assemblyPath, NativeMethods.GENERIC_READ, FileShare.Read, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
149                 if (fileHandle == NativeMethods.InvalidIntPtr)
150                     return snLevel;
151 
152                 // if it's not a disk file, exit
153                 if (NativeMethods.GetFileType(fileHandle) != NativeMethods.FILE_TYPE_DISK)
154                     return snLevel;
155 
156                 IntPtr fileMappingHandle = IntPtr.Zero;
157 
158                 try
159                 {
160                     fileMappingHandle = NativeMethods.CreateFileMapping(fileHandle, IntPtr.Zero, NativeMethods.PAGE_READONLY, 0, 0, null);
161                     if (fileMappingHandle == IntPtr.Zero)
162                         return snLevel;
163 
164                     IntPtr fileMappingBase = IntPtr.Zero;
165 
166                     try
167                     {
168                         fileMappingBase = NativeMethods.MapViewOfFile(fileMappingHandle, NativeMethods.FILE_MAP_READ, 0, 0, IntPtr.Zero);
169                         if (fileMappingBase == IntPtr.Zero)
170                             return snLevel;
171 
172                         // retrieve NT headers pointer from the file
173                         IntPtr ntHeader = NativeMethods.ImageNtHeader(fileMappingBase);
174 
175                         if (ntHeader == IntPtr.Zero)
176                             return snLevel;
177 
178                         // get relative virtual address of the COR20 header
179                         uint cor20HeaderRva = GetCor20HeaderRva(ntHeader);
180 
181                         if (cor20HeaderRva == 0)
182                             return snLevel;
183 
184                         IntPtr lastRvaSection = IntPtr.Zero;
185                         // get the pointer to the COR20 header structure
186                         IntPtr cor20HeaderPtr = NativeMethods.ImageRvaToVa(ntHeader, fileMappingBase, cor20HeaderRva, out lastRvaSection);
187 
188                         if (cor20HeaderPtr == IntPtr.Zero)
189                             return snLevel;
190 
191                         // get the COR20 structure itself
192                         NativeMethods.IMAGE_COR20_HEADER cor20Header = (NativeMethods.IMAGE_COR20_HEADER)Marshal.PtrToStructure(cor20HeaderPtr, typeof(NativeMethods.IMAGE_COR20_HEADER));
193 
194                         // and finally, examine it. If no space is allocated for strong name signature, assembly is not signed.
195                         if ((cor20Header.StrongNameSignature.VirtualAddress == 0) || (cor20Header.StrongNameSignature.Size == 0))
196                         {
197                             snLevel = StrongNameLevel.None;
198                         }
199                         else
200                         {
201                             // if there's allocated space and strong name flag is set, assembly is fully signed, or delay signed otherwise
202                             if ((cor20Header.Flags & NativeMethods.COMIMAGE_FLAGS_STRONGNAMESIGNED) != 0)
203                             {
204                                 snLevel = StrongNameLevel.FullySigned;
205                             }
206                             else
207                             {
208                                 snLevel = StrongNameLevel.DelaySigned;
209                             }
210                         }
211                     }
212                     finally
213                     {
214                         if (fileMappingBase != IntPtr.Zero)
215                         {
216                             NativeMethods.UnmapViewOfFile(fileMappingBase);
217                             fileMappingBase = IntPtr.Zero;
218                         }
219                     }
220                 }
221                 finally
222                 {
223                     if (fileMappingHandle != IntPtr.Zero)
224                     {
225                         NativeMethods.CloseHandle(fileMappingHandle);
226                         fileMappingHandle = IntPtr.Zero;
227                     }
228                 }
229             }
230             finally
231             {
232                 if (fileHandle != NativeMethods.InvalidIntPtr)
233                 {
234                     NativeMethods.CloseHandle(fileHandle);
235                     fileHandle = NativeMethods.InvalidIntPtr;
236                 }
237             }
238 
239             return snLevel;
240         }
241 
242         /// <summary>
243         /// Retrieves the relative virtual address of the COR20 header, given the address of the NT headers structure. The catch
244         /// here is that the NT headers struct can be either 32 or 64 bit version, and some fields have different sizes there. We
245         /// need to see if we're dealing with a 32bit header or a 64bit one first.
246         /// </summary>
247         /// <param name="ntHeadersPtr"></param>
248         /// <returns></returns>
GetCor20HeaderRva(IntPtr ntHeadersPtr)249         private static uint GetCor20HeaderRva(IntPtr ntHeadersPtr)
250         {
251             // read the first ushort in the optional header - we have an uint and IMAGE_FILE_HEADER preceding it
252             ushort optionalHeaderMagic = (ushort)Marshal.ReadInt16(ntHeadersPtr, Marshal.SizeOf<uint>() + Marshal.SizeOf<NativeMethods.IMAGE_FILE_HEADER>());
253 
254             // this should really be a structure, but NDP can't marshal fixed size struct arrays in a struct... ugh.
255             // this ulong corresponds to a IMAGE_DATA_DIRECTORY structure
256             ulong cor20DataDirectoryLong = 0;
257 
258             // see if we have a 32bit header or a 64bit header
259             if (optionalHeaderMagic == NativeMethods.IMAGE_NT_OPTIONAL_HDR32_MAGIC)
260             {
261                 // marshal data into the appropriate structure
262                 NativeMethods.IMAGE_NT_HEADERS32 ntHeader32 = (NativeMethods.IMAGE_NT_HEADERS32)Marshal.PtrToStructure(ntHeadersPtr, typeof(NativeMethods.IMAGE_NT_HEADERS32));
263                 cor20DataDirectoryLong = ntHeader32.optionalHeader.DataDirectory[NativeMethods.IMAGE_DIRECTORY_ENTRY_COMHEADER];
264             }
265             else if (optionalHeaderMagic == NativeMethods.IMAGE_NT_OPTIONAL_HDR64_MAGIC)
266             {
267                 // marshal data into the appropriate structure
268                 NativeMethods.IMAGE_NT_HEADERS64 ntHeader64 = (NativeMethods.IMAGE_NT_HEADERS64)Marshal.PtrToStructure(ntHeadersPtr, typeof(NativeMethods.IMAGE_NT_HEADERS64));
269                 cor20DataDirectoryLong = ntHeader64.optionalHeader.DataDirectory[NativeMethods.IMAGE_DIRECTORY_ENTRY_COMHEADER];
270             }
271             else
272             {
273                 Debug.Assert(false, "invalid file type!");
274                 return 0;
275             }
276 
277             // cor20DataDirectoryLong is really a IMAGE_DATA_DIRECTORY structure which I had to pack into an ulong
278             // (see comments for IMAGE_OPTIONAL_HEADER32/64 in NativeMethods.cs)
279             // this code extracts the virtualAddress (uint) and size (uint) fields from the ulong by doing simple
280             // bit masking/shifting ops
281             uint virtualAddress = (uint)(cor20DataDirectoryLong & 0x00000000ffffffff);
282             uint size = (uint)(cor20DataDirectoryLong >> 32);
283 
284             return virtualAddress;
285         }
286     }
287 }
288