New tests.
[mono.git] / mcs / class / corlib / Mono.Security.Authenticode / AuthenticodeBase.cs
old mode 100755 (executable)
new mode 100644 (file)
index 4b48e26..c766628
@@ -2,10 +2,32 @@
 // AuthenticodeBase.cs: Authenticode signature base class
 //
 // Author:
-//     Sebastien Pouliot (spouliot@motus.com)
+//     Sebastien Pouliot <sebastien@ximian.com>
 //
 // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004, 2006 Novell, Inc (http://www.novell.com)
 //
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#if !MOONLIGHT
 
 using System;
 using System.IO;
@@ -21,7 +43,7 @@ namespace Mono.Security.Authenticode {
 #else
        public
 #endif
-       enum AuthenticodeAuthority {
+       enum Authority {
                Individual,
                Commercial,
                Maximum
@@ -36,63 +58,229 @@ namespace Mono.Security.Authenticode {
 
                public const string spcIndirectDataContext = "1.3.6.1.4.1.311.2.1.4";
 
-               protected byte[] rawData;
+               private byte[] fileblock;
+               private FileStream fs;
+               private int blockNo;
+               private int blockLength;
+               private int peOffset;
+               private int dirSecurityOffset;
+               private int dirSecuritySize;
+               private int coffSymbolTableOffset;
 
                public AuthenticodeBase ()
                {
+                       fileblock = new byte [4096];
                }
 
-               protected byte[] HashFile (string fileName, string hashName) 
+               internal int PEOffset {
+                       get {
+                               if (blockNo < 1)
+                                       ReadFirstBlock ();
+                               return peOffset;
+                       }
+               }
+
+               internal int CoffSymbolTableOffset {
+                       get {
+                               if (blockNo < 1)
+                                       ReadFirstBlock ();
+                               return coffSymbolTableOffset;
+                       }
+               }
+
+               internal int SecurityOffset {
+                       get {
+                               if (blockNo < 1)
+                                       ReadFirstBlock ();
+                               return dirSecurityOffset;
+                       }
+               }
+
+               internal void Open (string filename)
                {
-                       FileStream fs = new FileStream (fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
-                       byte[] file = new byte [fs.Length];
-                       fs.Read (file, 0, file.Length);
-                       fs.Close ();
+                       if (fs != null)
+                               Close ();
+                       fs = new FileStream (filename, FileMode.Open, FileAccess.Read, FileShare.Read);
+               }
 
-                       // MZ - DOS header
-                       if (BitConverter.ToUInt16 (file, 0) != 0x5A4D)
-                               return null;
+               internal void Close ()
+               {
+                       if (fs != null) {
+                               fs.Close ();
+                               fs = null;
+                               blockNo = 0;
+                       }
+               }
 
-                       // find offset of PE header
-                       int peOffset = BitConverter.ToInt32 (file, 60);
-                       if (peOffset > file.Length)
-                               return null;
+               internal bool ReadFirstBlock ()
+               {
+                       if (fs == null)
+                               return false;
 
-                       // PE - NT header
-                       if (BitConverter.ToUInt16 (file, peOffset) != 0x4550)
-                               return null;
+                       fs.Position = 0;
+                       // read first block - it will include (100% sure) 
+                       // the MZ header and (99.9% sure) the PE header
+                       blockLength = fs.Read (fileblock, 0, fileblock.Length);
+                       blockNo = 1;
+                       if (blockLength < 64)
+                               return false;   // invalid PE file
+
+                       // 1. Validate the MZ header informations
+                       // 1.1. Check for magic MZ at start of header
+                       if (BitConverterLE.ToUInt16 (fileblock, 0) != 0x5A4D)
+                               return false;
+
+                       // 1.2. Find the offset of the PE header
+                       peOffset = BitConverterLE.ToInt32 (fileblock, 60);
+                       if (peOffset > fileblock.Length) {
+                               // just in case (0.1%) this can actually happen
+                               string msg = String.Format (Locale.GetText (
+                                       "Header size too big (> {0} bytes)."),
+                                       fileblock.Length);
+                               throw new NotSupportedException (msg);
+                       }
+                       if (peOffset > fs.Length)
+                               return false;
+
+                       // 2. Read between DOS header and first part of PE header
+                       // 2.1. Check for magic PE at start of header
+                       //      PE - NT header ('P' 'E' 0x00 0x00)
+                       if (BitConverterLE.ToUInt32 (fileblock, peOffset) != 0x4550)
+                               return false;
+
+                       // 2.2. Locate IMAGE_DIRECTORY_ENTRY_SECURITY (offset and size)
+                       dirSecurityOffset = BitConverterLE.ToInt32 (fileblock, peOffset + 152);
+                       dirSecuritySize = BitConverterLE.ToInt32 (fileblock, peOffset + 156);
 
-                       // IMAGE_DIRECTORY_ENTRY_SECURITY
-                       int dirSecurityOffset = BitConverter.ToInt32 (file, peOffset + 152);
-                       int dirSecuritySize = BitConverter.ToInt32 (file, peOffset + 156);
+                       // COFF symbol tables are deprecated - we'll strip them if we see them!
+                       // (otherwise the signature won't work on MS and we don't want to support COFF for that)
+                       coffSymbolTableOffset = BitConverterLE.ToInt32 (fileblock, peOffset + 12);
+
+                       return true;
+               }
+
+               internal byte[] GetSecurityEntry () 
+               {
+                       if (blockNo < 1)
+                               ReadFirstBlock ();
 
                        if (dirSecuritySize > 8) {
-                               rawData = new byte [dirSecuritySize - 8];
-                               Array.Copy (file, dirSecurityOffset + 8, rawData, 0, rawData.Length);
-/* DEBUG 
-                       FileStream debug = new FileStream (fileName + ".sig", FileMode.Create, FileAccess.Write);
-                       debug.Write (rawData, 0, rawData.Length);
-                       debug.Close ();*/
+                               // remove header from size (not ASN.1 based)
+                               byte[] secEntry = new byte [dirSecuritySize - 8];
+                               // position after header and read entry
+                               fs.Position = dirSecurityOffset + 8;
+                               fs.Read (secEntry, 0, secEntry.Length);
+                               return secEntry;
                        }
-                       else
-                               rawData = null;
+                       return null;
+               }
+
+               internal byte[] GetHash (HashAlgorithm hash)
+               {
+                       if (blockNo < 1)
+                               ReadFirstBlock ();
+                       fs.Position = blockLength;
 
-                       HashAlgorithm hash = HashAlgorithm.Create (hashName);
-                       // 0 to 215 (216) then skip 4 (checksum)
+                       // hash the rest of the file
+                       long n;
+                       int addsize = 0;
+                       // minus any authenticode signature (with 8 bytes header)
+                       if (dirSecurityOffset > 0) {
+                               // it is also possible that the signature block 
+                               // starts within the block in memory (small EXE)
+                               if (dirSecurityOffset < blockLength) {
+                                       blockLength = dirSecurityOffset;
+                                       n = 0;
+                               } else {
+                                       n = dirSecurityOffset - blockLength;
+                               }
+                       } else if (coffSymbolTableOffset > 0) {
+                               fileblock[PEOffset + 12] = 0;
+                               fileblock[PEOffset + 13] = 0;
+                               fileblock[PEOffset + 14] = 0;
+                               fileblock[PEOffset + 15] = 0;
+                               fileblock[PEOffset + 16] = 0;
+                               fileblock[PEOffset + 17] = 0;
+                               fileblock[PEOffset + 18] = 0;
+                               fileblock[PEOffset + 19] = 0;
+                               // it is also possible that the signature block 
+                               // starts within the block in memory (small EXE)
+                               if (coffSymbolTableOffset < blockLength) {
+                                       blockLength = coffSymbolTableOffset;
+                                       n = 0;
+                               } else {
+                                       n = coffSymbolTableOffset - blockLength;
+                               }
+                       } else {
+                               addsize = (int) (fs.Length & 7);
+                               if (addsize > 0)
+                                       addsize = 8 - addsize;
+                               
+                               n = fs.Length - blockLength;
+                       }
+
+                       // Authenticode(r) gymnastics
+                       // Hash from (generally) 0 to 215 (216 bytes)
                        int pe = peOffset + 88;
-                       hash.TransformBlock (file, 0, pe, file, 0);
+                       hash.TransformBlock (fileblock, 0, pe, fileblock, 0);
+                       // then skip 4 for checksum
                        pe += 4;
-                       // 220 to 279 (60) then skip 8 (IMAGE_DIRECTORY_ENTRY_SECURITY)
-                       hash.TransformBlock (file, pe, 60, file, pe);
+                       // Continue hashing from (generally) 220 to 279 (60 bytes)
+                       hash.TransformBlock (fileblock, pe, 60, fileblock, pe);
+                       // then skip 8 bytes for IMAGE_DIRECTORY_ENTRY_SECURITY
                        pe += 68;
-                       // 288 to end of file
-                       int n = file.Length - pe;
-                       // minus any authenticode signature (with 8 bytes header)
-                       if (dirSecurityOffset != 0)
-                               n -= (dirSecuritySize);
-                       hash.TransformFinalBlock (file, pe, n);
 
+                       // everything is present so start the hashing
+                       if (n == 0) {
+                               // hash the (only) block
+                               hash.TransformFinalBlock (fileblock, pe, blockLength - pe);
+                       }
+                       else {
+                               // hash the last part of the first (already in memory) block
+                               hash.TransformBlock (fileblock, pe, blockLength - pe, fileblock, pe);
+
+                               // hash by blocks of 4096 bytes
+                               long blocks = (n >> 12);
+                               int remainder = (int)(n - (blocks << 12));
+                               if (remainder == 0) {
+                                       blocks--;
+                                       remainder = 4096;
+                               }
+                               // blocks
+                               while (blocks-- > 0) {
+                                       fs.Read (fileblock, 0, fileblock.Length);
+                                       hash.TransformBlock (fileblock, 0, fileblock.Length, fileblock, 0);
+                               }
+                               // remainder
+                               if (fs.Read (fileblock, 0, remainder) != remainder)
+                                       return null;
+
+                               if (addsize > 0) {
+                                       hash.TransformBlock (fileblock, 0, remainder, fileblock, 0);
+                                       hash.TransformFinalBlock (new byte [addsize], 0, addsize);
+                               } else {
+                                       hash.TransformFinalBlock (fileblock, 0, remainder);
+                               }
+                       }
                        return hash.Hash;
                }
+
+               // for compatibility only
+               protected byte[] HashFile (string fileName, string hashName) 
+               {
+                       try {
+                               Open (fileName);
+                               HashAlgorithm hash = HashAlgorithm.Create (hashName);
+                               byte[] result = GetHash (hash);
+                               Close ();
+                               return result;
+                       }
+                       catch {
+                               return null;
+                       }
+               }
        }
 }
+
+#endif
+