diff options
author | jaykrell <jay.krell@cornell.edu> | 2018-04-18 06:47:38 +0300 |
---|---|---|
committer | Marek Safar <marek.safar@gmail.com> | 2018-04-18 06:47:38 +0300 |
commit | e97721555ebb2bb22de242d8764c614b0aff60cd (patch) | |
tree | a975ef8e683262ab6a82f07c50d3c8128b8c4dda /mcs/class/Mono.Security | |
parent | 96a089ae5ead0d30c9a79d5fac99ad1aabef7f85 (diff) |
Teach sn.exe about PE32+, and while there ROM images and MS-DOS-less images. (#8232)
* Teach sn.exe about PE32+, and while there ROM images and MS-DOS-less.
See https://github.com/mono/mono/issues/8218.
* Make Error static.
* Remove .Close () redundant with using.
* Use helper function ReadMore and System.Array.Resize.
* Fix typos in comment.
* Move hash.Hash to fix 'Hash must be finalized before the hash value is retrieved.'.
* Cleanup.
Diffstat (limited to 'mcs/class/Mono.Security')
-rw-r--r-- | mcs/class/Mono.Security/Mono.Security/StrongName.cs | 327 |
1 files changed, 212 insertions, 115 deletions
diff --git a/mcs/class/Mono.Security/Mono.Security/StrongName.cs b/mcs/class/Mono.Security/Mono.Security/StrongName.cs index 3f37b750eb2..b7c1f0483ee 100644 --- a/mcs/class/Mono.Security/Mono.Security/StrongName.cs +++ b/mcs/class/Mono.Security/Mono.Security/StrongName.cs @@ -278,132 +278,238 @@ namespace Mono.Security { UInt32 p = BitConverterLE.ToUInt32 (headers, i * 40 + 20); UInt32 s = BitConverterLE.ToUInt32 (headers, i * 40 + 12); int l = (int) BitConverterLE.ToUInt32 (headers, i * 40 + 8); - if ((s <= r) && (r < s + l)) { + if ((s <= r) && (r < s + l)) return p + r - s; - } } return 0; } - internal StrongNameSignature StrongHash (Stream stream, StrongNameOptions options) + private static StrongNameSignature Error (string a) { - StrongNameSignature info = new StrongNameSignature (); + //Console.WriteLine (a); + return null; + } - HashAlgorithm hash = HashAlgorithm.Create (TokenAlgorithm); - CryptoStream cs = new CryptoStream (Stream.Null, hash, CryptoStreamMode.Write); + private static byte[] ReadMore (Stream stream, byte[] a, int newSize) + { + int oldSize = a.Length; + Array.Resize (ref a, newSize); + if (newSize <= oldSize) + return a; + int diff = newSize - oldSize; + return (stream.Read (a, oldSize, diff) == diff) ? a : null; + } - // MS-DOS Header - always 128 bytes + internal StrongNameSignature StrongHash (Stream stream, StrongNameOptions options) + { + // Bing "msdn pecoff". + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms680547(v=vs.85).aspx + // Very many of the magic constants and names, funny or otherwise, come from this. // ref: Section 24.2.1, Partition II Metadata - byte[] mz = new byte [128]; - stream.Read (mz, 0, 128); - if (BitConverterLE.ToUInt16 (mz, 0) != 0x5a4d) - return null; - UInt32 peHeader = BitConverterLE.ToUInt32 (mz, 60); - cs.Write (mz, 0, 128); - if (peHeader != 128) { - byte[] mzextra = new byte [peHeader - 128]; - stream.Read (mzextra, 0, mzextra.Length); - cs.Write (mzextra, 0, mzextra.Length); - } - - // PE File Header - always 248 bytes // ref: Section 24.2.2, Partition II Metadata - byte[] pe = new byte [248]; - stream.Read (pe, 0, 248); - if (BitConverterLE.ToUInt32 (pe, 0) != 0x4550) - return null; - if (BitConverterLE.ToUInt16 (pe, 4) != 0x14c) - return null; - // MUST zeroize both CheckSum and Security Directory - byte[] v = new byte [8]; - Buffer.BlockCopy (v, 0, pe, 88, 4); - Buffer.BlockCopy (v, 0, pe, 152, 8); - cs.Write (pe, 0, 248); - - UInt16 numSection = BitConverterLE.ToUInt16 (pe, 6); - int sectionLength = (numSection * 40); - byte[] sectionHeaders = new byte [sectionLength]; - stream.Read (sectionHeaders, 0, sectionLength); - cs.Write (sectionHeaders, 0, sectionLength); - - UInt32 cliHeaderRVA = BitConverterLE.ToUInt32 (pe, 232); - UInt32 cliHeaderPos = RVAtoPosition (cliHeaderRVA, numSection, sectionHeaders); - int cliHeaderSiz = (int) BitConverterLE.ToUInt32 (pe, 236); - - // CLI Header // ref: Section 24.3.3, Partition II Metadata - byte[] cli = new byte [cliHeaderSiz]; - stream.Position = cliHeaderPos; - stream.Read (cli, 0, cliHeaderSiz); - - UInt32 strongNameSignatureRVA = BitConverterLE.ToUInt32 (cli, 32); - info.SignaturePosition = RVAtoPosition (strongNameSignatureRVA, numSection, sectionHeaders); - info.SignatureLength = BitConverterLE.ToUInt32 (cli, 36); - - UInt32 metadataRVA = BitConverterLE.ToUInt32 (cli, 8); - info.MetadataPosition = RVAtoPosition (metadataRVA, numSection, sectionHeaders); - info.MetadataLength = BitConverterLE.ToUInt32 (cli, 12); - - if (options == StrongNameOptions.Metadata) { - cs.Close (); - hash.Initialize (); - byte[] metadata = new byte [info.MetadataLength]; - stream.Position = info.MetadataPosition; - stream.Read (metadata, 0, metadata.Length); - info.Hash = hash.ComputeHash (metadata); - return info; - } - - // now we hash every section EXCEPT the signature block - for (int i=0; i < numSection; i++) { - UInt32 start = BitConverterLE.ToUInt32 (sectionHeaders, i * 40 + 20); - int length = (int) BitConverterLE.ToUInt32 (sectionHeaders, i * 40 + 16); - byte[] section = new byte [length]; - stream.Position = start; - stream.Read (section, 0, length); - if ((start <= info.SignaturePosition) && (info.SignaturePosition < start + length)) { - // hash before the signature - int before = (int)(info.SignaturePosition - start); - if (before > 0) { - cs.Write (section, 0, before); - } - // copy signature - info.Signature = new byte [info.SignatureLength]; - Buffer.BlockCopy (section, before, info.Signature, 0, (int)info.SignatureLength); - Array.Reverse (info.Signature); - // hash after the signature - int s = (int)(before + info.SignatureLength); - int after = (int)(length - s); - if (after > 0) { - cs.Write (section, s, after); + + // Read MS-DOS header. + + const int mzSize = 64; + byte[] mz = new byte [mzSize]; + + int peHeader = 0; + int mzRead = stream.Read (mz, 0, mzSize); + + if (mzRead == mzSize && mz [0] == (byte)'M' && mz [1] == (byte)'Z') { // 0x5a4d + peHeader = BitConverterLE.ToInt32 (mz, 60); + if (peHeader < mzSize) + return Error ("peHeader_lt_64"); + + // Read MS-DOS stub. + + mz = ReadMore (stream, mz, peHeader); + if (mz == null) + return Error ("read_mz2_failed"); + } else if (mzRead >= 4 && mz [0] == (byte)'P' && mz [1] == (byte)'E' && mz [2] == 0 && mz [3] == 0) { // 0x4550 + // MS-DOS header/stub can be omitted and just start with PE, though it is rare. + stream.Position = 0; + mz = new byte [0]; + } else + return Error ("read_mz_or_mzsig_failed"); + + // PE File Header + // PE signature 4 bytes + // file header 20 bytes (really, at this point) + // optional header varies in size and its size is in the file header + // "optional" means "not in .obj files", but always in .dll/.exes + + const int sizeOfPeSignature = 4; + const int sizeOfFileHeader = 20; + const int sizeOfOptionalHeaderMagic = 2; + const int offsetOfFileHeader = sizeOfPeSignature; + const int offsetOfOptionalHeader = sizeOfPeSignature + sizeOfFileHeader; + int sizeOfOptionalHeader = sizeOfOptionalHeaderMagic; // initial minimum + int minimumHeadersSize = offsetOfOptionalHeader + sizeOfOptionalHeader; + byte[] pe = new byte [minimumHeadersSize]; + if (stream.Read (pe, 0, minimumHeadersSize) != minimumHeadersSize + || pe [0] != (byte)'P' || pe [1] != (byte)'E' || pe [2] != 0 || pe [3] != 0) // 0x4550 + return Error ("read_minimumHeadersSize_or_pesig_failed"); + + sizeOfOptionalHeader = BitConverterLE.ToUInt16 (pe, offsetOfFileHeader + 16); + if (sizeOfOptionalHeader < sizeOfOptionalHeaderMagic) + return Error ($"sizeOfOptionalHeader_lt_2 ${sizeOfOptionalHeader}"); + + int headersSize = offsetOfOptionalHeader + sizeOfOptionalHeader; + if (headersSize < offsetOfOptionalHeader) // check overflow + return Error ("headers_overflow"); + + // Read the rest of the NT headers (i.e. the rest of the optional header). + + pe = ReadMore (stream, pe, headersSize); + if (pe == null) + return Error ("read_pe2_failed"); + + uint magic = BitConverterLE.ToUInt16 (pe, offsetOfOptionalHeader); + + // Refer to PE32+ as PE64 for brevity. + // PE64 proposal that widened more fields was rejected. + // Between PE32 and PE32+: + // Some fields are the same size and offset. For example the entire + // MS-DOS header, FileHeader, and section headers, and some of the optional header. + // Some fields are PE32-only (BaseOfData). + // Some fields are constant size, some are pointer size. + // Relative virtual addresses and file offsets are always 4 bytes. + // Some fields offsets are offset by 4, 8, or 12, but mostly 0 or 16, + // and it so happens that the 4/8/12-offset fields are less interesting. + int pe64 = 0; + bool rom = false; + if (magic == 0x10B) { + // nothing + } else if (magic == 0x20B) + pe64 = 16; + else if (magic == 0x107) + rom = true; + else + return Error ("bad_magic_value"); + + uint numberOfRvaAndSizes = 0; + + if (!rom) { // ROM images have no data directories or checksum. + if (sizeOfOptionalHeader >= offsetOfOptionalHeader + 92 + pe64 + 4) + numberOfRvaAndSizes = BitConverterLE.ToUInt32 (pe, offsetOfOptionalHeader + 92 + pe64); + + // Clear CheckSum and Security Directory if present. + // CheckSum is located the same for PE32+, all data directories are not. + + for (int i = 64; i < sizeOfOptionalHeader && i < 68; ++i) + pe [offsetOfOptionalHeader + i] = 0; + + for (int i = 128 + pe64; i < sizeOfOptionalHeader && i < 128 + 8 + pe64; ++i) + pe [offsetOfOptionalHeader + i] = 0; + } + + // Read the section headers if present (an image can have no sections, just headers). + + const int sizeOfSectionHeader = 40; + int numberOfSections = BitConverterLE.ToUInt16 (pe, offsetOfFileHeader + 2); + byte[] sectionHeaders = new byte [numberOfSections * sizeOfSectionHeader]; + if (stream.Read (sectionHeaders, 0, sectionHeaders.Length) != sectionHeaders.Length) + return Error ("read_section_headers_failed"); + + // Read the CLR header if present. + + uint SignaturePosition = 0; + uint SignatureLength = 0; + uint MetadataPosition = 0; + uint MetadataLength = 0; + + if (15 < numberOfRvaAndSizes && sizeOfOptionalHeader >= 216 + pe64) { + uint cliHeaderRVA = BitConverterLE.ToUInt32 (pe, offsetOfOptionalHeader + 208 + pe64); + uint cliHeaderPos = RVAtoPosition (cliHeaderRVA, numberOfSections, sectionHeaders); + int cliHeaderSiz = BitConverterLE.ToInt32 (pe, offsetOfOptionalHeader + 208 + 4 + pe64); + + // CLI Header + // ref: Section 24.3.3, Partition II Metadata + var cli = new byte [cliHeaderSiz]; + stream.Position = cliHeaderPos; + if (stream.Read (cli, 0, cliHeaderSiz) != cliHeaderSiz) + return Error ("read_cli_header_failed"); + + uint strongNameSignatureRVA = BitConverterLE.ToUInt32 (cli, 32); + SignaturePosition = RVAtoPosition (strongNameSignatureRVA, numberOfSections, sectionHeaders); + SignatureLength = BitConverterLE.ToUInt32 (cli, 36); + + uint metadataRVA = BitConverterLE.ToUInt32 (cli, 8); + MetadataPosition = RVAtoPosition (metadataRVA, numberOfSections, sectionHeaders); + MetadataLength = BitConverterLE.ToUInt32 (cli, 12); + } + + StrongNameSignature info = new StrongNameSignature (); + info.SignaturePosition = SignaturePosition; + info.SignatureLength = SignatureLength; + info.MetadataPosition = MetadataPosition; + info.MetadataLength = MetadataLength; + + using (HashAlgorithm hash = HashAlgorithm.Create (TokenAlgorithm)) { + if (options == StrongNameOptions.Metadata) { + hash.Initialize (); + byte[] metadata = new byte [MetadataLength]; + stream.Position = MetadataPosition; + if (stream.Read (metadata, 0, (int)MetadataLength) != (int)MetadataLength) + return Error ("read_cli_metadata_failed"); + info.Hash = hash.ComputeHash (metadata); + return info; + } + + using (CryptoStream cs = new CryptoStream (Stream.Null, hash, CryptoStreamMode.Write)) { + cs.Write (mz, 0, mz.Length); // Hash MS-DOS header/stub despite that stub is not run. + cs.Write (pe, 0, pe.Length); + cs.Write (sectionHeaders, 0, sectionHeaders.Length); + + // now we hash every section EXCEPT the signature block + for (int i=0; i < numberOfSections; i++) { + UInt32 start = BitConverterLE.ToUInt32 (sectionHeaders, i * sizeOfSectionHeader + 20); + int length = BitConverterLE.ToInt32 (sectionHeaders, i * sizeOfSectionHeader + 16); + byte[] section = new byte [length]; + stream.Position = start; + if (stream.Read (section, 0, length) != length) + return Error ("read_section_failed"); + // The signature is assumed not to straddle sections. + if ((start <= SignaturePosition) && (SignaturePosition < start + (uint)length)) { + // hash before the signature + int before = (int)(SignaturePosition - start); + if (before > 0) + cs.Write (section, 0, before); + + // copy signature + info.Signature = new byte [SignatureLength]; + Buffer.BlockCopy (section, before, info.Signature, 0, (int)SignatureLength); + Array.Reverse (info.Signature); + // hash after the signature + int s = (int)(before + SignatureLength); + int after = (int)(length - s); + if (after > 0) + cs.Write (section, s, after); + } + else + cs.Write (section, 0, length); } } - else - cs.Write (section, 0, length); + info.Hash = hash.Hash; } - - cs.Close (); - info.Hash = hash.Hash; return info; } // return the same result as the undocumented and unmanaged GetHashFromAssemblyFile public byte[] Hash (string fileName) { - FileStream fs = File.OpenRead (fileName); - StrongNameSignature sn = StrongHash (fs, StrongNameOptions.Metadata); - fs.Close (); - - return sn.Hash; + using (FileStream fs = File.OpenRead (fileName)) { + return StrongHash (fs, StrongNameOptions.Metadata).Hash; + } } public bool Sign (string fileName) { - bool result = false; StrongNameSignature sn; using (FileStream fs = File.OpenRead (fileName)) { sn = StrongHash (fs, StrongNameOptions.Signature); - fs.Close (); } if (sn.Hash == null) return false; @@ -422,28 +528,22 @@ namespace Mono.Security { using (FileStream fs = File.OpenWrite (fileName)) { fs.Position = sn.SignaturePosition; fs.Write (signature, 0, signature.Length); - fs.Close (); - result = true; } - return result; + return true; } public bool Verify (string fileName) { - bool result = false; using (FileStream fs = File.OpenRead (fileName)) { - result = Verify (fs); - fs.Close (); + return Verify (fs); } - return result; } public bool Verify (Stream stream) { StrongNameSignature sn = StrongHash (stream, StrongNameOptions.Signature); - if (sn.Hash == null) { + if (sn.Hash == null) return false; - } try { AssemblyHashAlgorithm algorithm = AssemblyHashAlgorithm.SHA1; @@ -482,23 +582,20 @@ namespace Mono.Security { return false; byte[] publicKey = StrongNameManager.GetMappedPublicKey (an.GetPublicKeyToken ()); - if ((publicKey == null) || (publicKey.Length < 12)) { + if (publicKey == null || publicKey.Length < 12) { // no mapping publicKey = an.GetPublicKey (); - if ((publicKey == null) || (publicKey.Length < 12)) + if (publicKey == null || publicKey.Length < 12) return false; } // Note: MustVerify is based on the original token (by design). Public key // remapping won't affect if the assembly is verified or not. - if (!StrongNameManager.MustVerify (an)) { + if (!StrongNameManager.MustVerify (an)) return true; - } RSA rsa = CryptoConvert.FromCapiPublicKeyBlob (publicKey, 12); - StrongName sn = new StrongName (rsa); - bool result = sn.Verify (assemblyName); - return result; + return new StrongName (rsa).Verify (assemblyName); } catch { // no exception allowed |