// dnlib: See LICENSE.txt for more info
using System;
using System.IO;
using System.Security.Cryptography;
namespace dnlib.DotNet {
///
/// Strong name signs an assembly. It supports normal strong name signing and the new
/// (.NET Framework 4.5) enhanced strong name signing.
///
public readonly struct StrongNameSigner {
readonly Stream stream;
readonly long baseOffset;
///
/// Constructor
///
/// .NET PE file stream
public StrongNameSigner(Stream stream)
: this(stream, 0) {
}
///
/// Constructor
///
/// .NET PE file stream
/// Offset in of the first byte of
/// the PE file.
public StrongNameSigner(Stream stream, long baseOffset) {
this.stream = stream;
this.baseOffset = baseOffset;
}
///
/// Calculates the strong name signature and writes it to the stream. The signature
/// is also returned.
///
/// Strong name key used for signing
/// Offset (relative to the start of the PE file) of the strong
/// name signature.
/// The strong name signature
public byte[] WriteSignature(StrongNameKey snk, long snSigOffset) {
var sign = CalculateSignature(snk, snSigOffset);
stream.Position = baseOffset + snSigOffset;
stream.Write(sign, 0, sign.Length);
return sign;
}
///
/// Calculates and returns the strong name signature
///
/// Strong name key used for signing
/// Offset (relative to start of PE file) of the strong
/// name signature.
/// The strong name signature
public byte[] CalculateSignature(StrongNameKey snk, long snSigOffset) {
uint snSigSize = (uint)snk.SignatureSize;
var hashAlg = snk.HashAlgorithm == 0 ? AssemblyHashAlgorithm.SHA1 : snk.HashAlgorithm;
var hash = StrongNameHashData(hashAlg, snSigOffset, snSigSize);
var snSig = GetStrongNameSignature(snk, hashAlg, hash);
if (snSig.Length != snSigSize)
throw new InvalidOperationException("Invalid strong name signature size");
return snSig;
}
///
/// Strong name hashes the .NET file
///
/// Hash algorithm
/// Strong name sig offset (relative to start of .NET PE file)
/// Size of strong name signature
/// The strong name hash of the .NET file
byte[] StrongNameHashData(AssemblyHashAlgorithm hashAlg, long snSigOffset, uint snSigSize) {
var reader = new BinaryReader(stream);
snSigOffset += baseOffset;
long snSigOffsetEnd = snSigOffset + snSigSize;
using (var hasher = new AssemblyHash(hashAlg)) {
var buffer = new byte[0x8000];
// Hash the DOS header. It's defined to be all data from the start of
// the file up to the NT headers.
stream.Position = baseOffset + 0x3C;
uint ntHeadersOffs = reader.ReadUInt32();
stream.Position = baseOffset;
hasher.Hash(stream, ntHeadersOffs, buffer);
// Hash NT headers, but hash authenticode + checksum as 0s
stream.Position += 6;
int numSections = reader.ReadUInt16();
stream.Position -= 8;
hasher.Hash(stream, 0x18, buffer); // magic + FileHeader
bool is32bit = reader.ReadUInt16() == 0x010B;
stream.Position -= 2;
int optHeaderSize = is32bit ? 0x60 : 0x70;
if (stream.Read(buffer, 0, optHeaderSize) != optHeaderSize)
throw new IOException("Could not read data");
// Clear checksum
for (int i = 0; i < 4; i++)
buffer[0x40 + i] = 0;
hasher.Hash(buffer, 0, optHeaderSize);
const int imageDirsSize = 16 * 8;
if (stream.Read(buffer, 0, imageDirsSize) != imageDirsSize)
throw new IOException("Could not read data");
// Clear authenticode data dir
for (int i = 0; i < 8; i++)
buffer[4 * 8 + i] = 0;
hasher.Hash(buffer, 0, imageDirsSize);
// Hash section headers
long sectHeadersOffs = stream.Position;
hasher.Hash(stream, (uint)numSections * 0x28, buffer);
// Hash all raw section data but make sure we don't hash the location
// where the strong name signature will be stored.
for (int i = 0; i < numSections; i++) {
stream.Position = sectHeadersOffs + i * 0x28 + 0x10;
uint sizeOfRawData = reader.ReadUInt32();
uint pointerToRawData = reader.ReadUInt32();
stream.Position = baseOffset + pointerToRawData;
while (sizeOfRawData > 0) {
var pos = stream.Position;
if (snSigOffset <= pos && pos < snSigOffsetEnd) {
uint skipSize = (uint)(snSigOffsetEnd - pos);
if (skipSize >= sizeOfRawData)
break;
sizeOfRawData -= skipSize;
stream.Position += skipSize;
continue;
}
if (pos >= snSigOffsetEnd) {
hasher.Hash(stream, sizeOfRawData, buffer);
break;
}
uint maxLen = (uint)Math.Min(snSigOffset - pos, sizeOfRawData);
hasher.Hash(stream, maxLen, buffer);
sizeOfRawData -= maxLen;
}
}
return hasher.ComputeHash();
}
}
///
/// Returns the strong name signature
///
/// Strong name key
/// Hash algorithm
/// Strong name hash of the .NET PE file
/// Strong name signature
byte[] GetStrongNameSignature(StrongNameKey snk, AssemblyHashAlgorithm hashAlg, byte[] hash) {
using (var rsa = snk.CreateRSA()) {
var rsaFmt = new RSAPKCS1SignatureFormatter(rsa);
string hashName = hashAlg.GetName() ?? AssemblyHashAlgorithm.SHA1.GetName();
rsaFmt.SetHashAlgorithm(hashName);
var snSig = rsaFmt.CreateSignature(hash);
Array.Reverse(snSig);
return snSig;
}
}
}
}