|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security;
using Microsoft.Build.Shared;
using Microsoft.Build.Utilities;
#nullable disable
namespace Microsoft.Build.Tasks
{
/// <summary>
/// Possible strong name states of an assembly
/// </summary>
internal enum StrongNameLevel
{
None,
DelaySigned,
FullySigned,
Unknown,
}
/// <summary>
/// Strong naming utilities.
/// </summary>
internal static class StrongNameUtils
{
/// <summary>
/// Reads contents of a key file. Reused from vsdesigner code.
/// </summary>
internal static void ReadKeyFile(TaskLoggingHelper log, string keyFile, out StrongNameKeyPair keyPair, out byte[] publicKey)
{
// Initialize parameters
keyPair = null;
publicKey = null;
byte[] keyFileContents;
try
{
// Read the stuff from the file stream
using (FileStream fs = new FileStream(keyFile, FileMode.Open, FileAccess.Read, FileShare.Read))
{
int fileLength = (int)fs.Length;
keyFileContents = new byte[fileLength];
#pragma warning disable CA2022 // Avoid inexact read with 'Stream.Read'
// TODO: Read the count of read bytes and check if it matches the expected length, if not raise an exception
fs.Read(keyFileContents, 0, fileLength);
#pragma warning restore CA2022 // Avoid inexact read with 'Stream.Read'
}
}
catch (ArgumentException e)
{
log.LogErrorWithCodeFromResources("StrongNameUtils.KeyFileReadFailure", keyFile);
log.LogErrorFromException(e);
throw new StrongNameException(e);
}
catch (IOException e)
{
log.LogErrorWithCodeFromResources("StrongNameUtils.KeyFileReadFailure", keyFile);
log.LogErrorFromException(e);
throw new StrongNameException(e);
}
catch (SecurityException e)
{
log.LogErrorWithCodeFromResources("StrongNameUtils.KeyFileReadFailure", keyFile);
log.LogErrorFromException(e);
throw new StrongNameException(e);
}
// Make a new key pair from what we read
var snp = new StrongNameKeyPair(keyFileContents);
// If anything fails reading the public key portion of the strong name key pair, then
// assume that keyFile contained only the public key portion of the public/private pair.
try
{
publicKey = snp.PublicKey;
// If we didn't throw up to this point then we have a valid public/private key pair,
// so assign the object just created above to the out parameter.
keyPair = snp;
}
catch (ArgumentException)
{
publicKey = keyFileContents;
}
}
/// <summary>
/// Given a key file or container, extract private/public key data. Reused from vsdesigner code.
/// </summary>
internal static void GetStrongNameKey(TaskLoggingHelper log, string keyFile, string keyContainer, out StrongNameKeyPair keyPair, out byte[] publicKey)
{
// Gets either a strong name key pair from the key file or a key container.
// If keyFile and keyContainer are both null/zero length then returns null.
// Initialize parameters
keyPair = null;
publicKey = null;
if (!string.IsNullOrEmpty(keyContainer))
{
try
{
keyPair = new StrongNameKeyPair(keyContainer);
publicKey = keyPair.PublicKey;
}
catch (SecurityException e)
{
log.LogErrorWithCodeFromResources("StrongNameUtils.BadKeyContainer", keyContainer);
log.LogErrorFromException(e);
throw new StrongNameException(e);
}
catch (ArgumentException e)
{
log.LogErrorWithCodeFromResources("StrongNameUtils.BadKeyContainer", keyContainer);
log.LogErrorFromException(e);
throw new StrongNameException(e);
}
}
else if (!string.IsNullOrEmpty(keyFile))
{
ReadKeyFile(log, keyFile, out keyPair, out publicKey);
}
}
/// <summary>
/// Given an assembly path, determine if the assembly is [delay] signed or not. This code is based on similar unmanaged
/// routines in vsproject and sn.exe (ndp tools) codebases.
/// </summary>
/// <param name="assemblyPath"></param>
/// <returns></returns>
internal static StrongNameLevel GetAssemblyStrongNameLevel(string assemblyPath)
{
ErrorUtilities.VerifyThrowArgumentNull(assemblyPath);
StrongNameLevel snLevel = StrongNameLevel.Unknown;
IntPtr fileHandle = NativeMethods.InvalidIntPtr;
try
{
// open the assembly
fileHandle = NativeMethods.CreateFile(assemblyPath, NativeMethods.GENERIC_READ, FileShare.Read, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
if (fileHandle == NativeMethods.InvalidIntPtr)
{
return snLevel;
}
// if it's not a disk file, exit
if (NativeMethods.GetFileType(fileHandle) != NativeMethods.FILE_TYPE_DISK)
{
return snLevel;
}
IntPtr fileMappingHandle = IntPtr.Zero;
try
{
fileMappingHandle = NativeMethods.CreateFileMapping(fileHandle, IntPtr.Zero, NativeMethods.PAGE_READONLY, 0, 0, null);
if (fileMappingHandle == IntPtr.Zero)
{
return snLevel;
}
IntPtr fileMappingBase = IntPtr.Zero;
try
{
fileMappingBase = NativeMethods.MapViewOfFile(fileMappingHandle, NativeMethods.FILE_MAP_READ, 0, 0, IntPtr.Zero);
if (fileMappingBase == IntPtr.Zero)
{
return snLevel;
}
// retrieve NT headers pointer from the file
IntPtr ntHeader = NativeMethods.ImageNtHeader(fileMappingBase);
if (ntHeader == IntPtr.Zero)
{
return snLevel;
}
// get relative virtual address of the COR20 header
uint cor20HeaderRva = GetCor20HeaderRva(ntHeader);
if (cor20HeaderRva == 0)
{
return snLevel;
}
// get the pointer to the COR20 header structure
IntPtr cor20HeaderPtr = NativeMethods.ImageRvaToVa(ntHeader, fileMappingBase, cor20HeaderRva, out _);
if (cor20HeaderPtr == IntPtr.Zero)
{
return snLevel;
}
// get the COR20 structure itself
NativeMethods.IMAGE_COR20_HEADER cor20Header = (NativeMethods.IMAGE_COR20_HEADER)Marshal.PtrToStructure(cor20HeaderPtr, typeof(NativeMethods.IMAGE_COR20_HEADER));
// and finally, examine it. If no space is allocated for strong name signature, assembly is not signed.
if ((cor20Header.StrongNameSignature.VirtualAddress == 0) || (cor20Header.StrongNameSignature.Size == 0))
{
snLevel = StrongNameLevel.None;
}
else
{
// if there's allocated space and strong name flag is set, assembly is fully signed, or delay signed otherwise
if ((cor20Header.Flags & NativeMethods.COMIMAGE_FLAGS_STRONGNAMESIGNED) != 0)
{
snLevel = StrongNameLevel.FullySigned;
}
else
{
snLevel = StrongNameLevel.DelaySigned;
}
}
}
finally
{
if (fileMappingBase != IntPtr.Zero)
{
NativeMethods.UnmapViewOfFile(fileMappingBase);
fileMappingBase = IntPtr.Zero;
}
}
}
finally
{
if (fileMappingHandle != IntPtr.Zero)
{
NativeMethods.CloseHandle(fileMappingHandle);
fileMappingHandle = IntPtr.Zero;
}
}
}
finally
{
if (fileHandle != NativeMethods.InvalidIntPtr)
{
NativeMethods.CloseHandle(fileHandle);
}
}
return snLevel;
}
/// <summary>
/// Retrieves the relative virtual address of the COR20 header, given the address of the NT headers structure. The catch
/// here is that the NT headers struct can be either 32 or 64 bit version, and some fields have different sizes there. We
/// need to see if we're dealing with a 32bit header or a 64bit one first.
/// </summary>
/// <param name="ntHeadersPtr"></param>
/// <returns></returns>
private static uint GetCor20HeaderRva(IntPtr ntHeadersPtr)
{
// read the first ushort in the optional header - we have an uint and IMAGE_FILE_HEADER preceding it
ushort optionalHeaderMagic = (ushort)Marshal.ReadInt16(ntHeadersPtr, Marshal.SizeOf<uint>() + Marshal.SizeOf<NativeMethods.IMAGE_FILE_HEADER>());
// this should really be a structure, but NDP can't marshal fixed size struct arrays in a struct... ugh.
// this ulong corresponds to a IMAGE_DATA_DIRECTORY structure
ulong cor20DataDirectoryLong;
// see if we have a 32bit header or a 64bit header
if (optionalHeaderMagic == NativeMethods.IMAGE_NT_OPTIONAL_HDR32_MAGIC)
{
// marshal data into the appropriate structure
NativeMethods.IMAGE_NT_HEADERS32 ntHeader32 = (NativeMethods.IMAGE_NT_HEADERS32)Marshal.PtrToStructure(ntHeadersPtr, typeof(NativeMethods.IMAGE_NT_HEADERS32));
cor20DataDirectoryLong = ntHeader32.optionalHeader.DataDirectory[NativeMethods.IMAGE_DIRECTORY_ENTRY_COMHEADER];
}
else if (optionalHeaderMagic == NativeMethods.IMAGE_NT_OPTIONAL_HDR64_MAGIC)
{
// marshal data into the appropriate structure
NativeMethods.IMAGE_NT_HEADERS64 ntHeader64 = (NativeMethods.IMAGE_NT_HEADERS64)Marshal.PtrToStructure(ntHeadersPtr, typeof(NativeMethods.IMAGE_NT_HEADERS64));
cor20DataDirectoryLong = ntHeader64.optionalHeader.DataDirectory[NativeMethods.IMAGE_DIRECTORY_ENTRY_COMHEADER];
}
else
{
Debug.Assert(false, "invalid file type!");
return 0;
}
// cor20DataDirectoryLong is really a IMAGE_DATA_DIRECTORY structure which I had to pack into an ulong
// (see comments for IMAGE_OPTIONAL_HEADER32/64 in NativeMethods.cs)
// this code extracts the virtualAddress (uint) and size (uint) fields from the ulong by doing simple
// bit masking/shifting ops
uint virtualAddress = (uint)(cor20DataDirectoryLong & 0x00000000ffffffff);
// uint size = (uint)(cor20DataDirectoryLong >> 32);
return virtualAddress;
}
}
}
|