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