1 // 2 // AuthenticodeBase.cs: Authenticode signature base class 3 // 4 // Author: 5 // Sebastien Pouliot <sebastien@ximian.com> 6 // 7 // (C) 2003 Motus Technologies Inc. (http://www.motus.com) 8 // Copyright (C) 2004, 2006 Novell, Inc (http://www.novell.com) 9 // 10 // Permission is hereby granted, free of charge, to any person obtaining 11 // a copy of this software and associated documentation files (the 12 // "Software"), to deal in the Software without restriction, including 13 // without limitation the rights to use, copy, modify, merge, publish, 14 // distribute, sublicense, and/or sell copies of the Software, and to 15 // permit persons to whom the Software is furnished to do so, subject to 16 // the following conditions: 17 // 18 // The above copyright notice and this permission notice shall be 19 // included in all copies or substantial portions of the Software. 20 // 21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 // 29 30 using System; 31 using System.IO; 32 using System.Security.Cryptography; 33 34 namespace Mono.Security.Authenticode { 35 36 // References: 37 // a. http://www.cs.auckland.ac.nz/~pgut001/pubs/authenticode.txt 38 39 #if INSIDE_CORLIB 40 internal 41 #else 42 public 43 #endif 44 enum Authority { 45 Individual, 46 Commercial, 47 Maximum 48 } 49 50 #if INSIDE_CORLIB 51 internal 52 #else 53 public 54 #endif 55 class AuthenticodeBase { 56 57 public const string spcIndirectDataContext = "1.3.6.1.4.1.311.2.1.4"; 58 59 private byte[] fileblock; 60 private FileStream fs; 61 private int blockNo; 62 private int blockLength; 63 private int peOffset; 64 private int dirSecurityOffset; 65 private int dirSecuritySize; 66 private int coffSymbolTableOffset; 67 AuthenticodeBase()68 public AuthenticodeBase () 69 { 70 fileblock = new byte [4096]; 71 } 72 73 internal int PEOffset { 74 get { 75 if (blockNo < 1) 76 ReadFirstBlock (); 77 return peOffset; 78 } 79 } 80 81 internal int CoffSymbolTableOffset { 82 get { 83 if (blockNo < 1) 84 ReadFirstBlock (); 85 return coffSymbolTableOffset; 86 } 87 } 88 89 internal int SecurityOffset { 90 get { 91 if (blockNo < 1) 92 ReadFirstBlock (); 93 return dirSecurityOffset; 94 } 95 } 96 Open(string filename)97 internal void Open (string filename) 98 { 99 if (fs != null) 100 Close (); 101 fs = new FileStream (filename, FileMode.Open, FileAccess.Read, FileShare.Read); 102 blockNo = 0; 103 } 104 Close()105 internal void Close () 106 { 107 if (fs != null) { 108 fs.Close (); 109 fs = null; 110 } 111 } 112 ReadFirstBlock()113 internal void ReadFirstBlock () 114 { 115 int error = ProcessFirstBlock (); 116 if (error != 0) { 117 string msg = Locale.GetText ("Cannot sign non PE files, e.g. .CAB or .MSI files (error {0}).", 118 error); 119 throw new NotSupportedException (msg); 120 } 121 } 122 ProcessFirstBlock()123 internal int ProcessFirstBlock () 124 { 125 if (fs == null) 126 return 1; 127 128 fs.Position = 0; 129 // read first block - it will include (100% sure) 130 // the MZ header and (99.9% sure) the PE header 131 blockLength = fs.Read (fileblock, 0, fileblock.Length); 132 blockNo = 1; 133 if (blockLength < 64) 134 return 2; // invalid PE file 135 136 // 1. Validate the MZ header informations 137 // 1.1. Check for magic MZ at start of header 138 if (BitConverterLE.ToUInt16 (fileblock, 0) != 0x5A4D) 139 return 3; 140 141 // 1.2. Find the offset of the PE header 142 peOffset = BitConverterLE.ToInt32 (fileblock, 60); 143 if (peOffset > fileblock.Length) { 144 // just in case (0.1%) this can actually happen 145 string msg = String.Format (Locale.GetText ( 146 "Header size too big (> {0} bytes)."), 147 fileblock.Length); 148 throw new NotSupportedException (msg); 149 } 150 if (peOffset > fs.Length) 151 return 4; 152 153 // 2. Read between DOS header and first part of PE header 154 // 2.1. Check for magic PE at start of header 155 // PE - NT header ('P' 'E' 0x00 0x00) 156 if (BitConverterLE.ToUInt32 (fileblock, peOffset) != 0x4550) 157 return 5; 158 159 // 2.2. Locate IMAGE_DIRECTORY_ENTRY_SECURITY (offset and size) 160 dirSecurityOffset = BitConverterLE.ToInt32 (fileblock, peOffset + 152); 161 dirSecuritySize = BitConverterLE.ToInt32 (fileblock, peOffset + 156); 162 163 // COFF symbol tables are deprecated - we'll strip them if we see them! 164 // (otherwise the signature won't work on MS and we don't want to support COFF for that) 165 coffSymbolTableOffset = BitConverterLE.ToInt32 (fileblock, peOffset + 12); 166 167 return 0; 168 } 169 GetSecurityEntry()170 internal byte[] GetSecurityEntry () 171 { 172 if (blockNo < 1) 173 ReadFirstBlock (); 174 175 if (dirSecuritySize > 8) { 176 // remove header from size (not ASN.1 based) 177 byte[] secEntry = new byte [dirSecuritySize - 8]; 178 // position after header and read entry 179 fs.Position = dirSecurityOffset + 8; 180 fs.Read (secEntry, 0, secEntry.Length); 181 return secEntry; 182 } 183 return null; 184 } 185 GetHash(HashAlgorithm hash)186 internal byte[] GetHash (HashAlgorithm hash) 187 { 188 if (blockNo < 1) 189 ReadFirstBlock (); 190 fs.Position = blockLength; 191 192 // hash the rest of the file 193 long n; 194 int addsize = 0; 195 // minus any authenticode signature (with 8 bytes header) 196 if (dirSecurityOffset > 0) { 197 // it is also possible that the signature block 198 // starts within the block in memory (small EXE) 199 if (dirSecurityOffset < blockLength) { 200 blockLength = dirSecurityOffset; 201 n = 0; 202 } else { 203 n = dirSecurityOffset - blockLength; 204 } 205 } else if (coffSymbolTableOffset > 0) { 206 fileblock[PEOffset + 12] = 0; 207 fileblock[PEOffset + 13] = 0; 208 fileblock[PEOffset + 14] = 0; 209 fileblock[PEOffset + 15] = 0; 210 fileblock[PEOffset + 16] = 0; 211 fileblock[PEOffset + 17] = 0; 212 fileblock[PEOffset + 18] = 0; 213 fileblock[PEOffset + 19] = 0; 214 // it is also possible that the signature block 215 // starts within the block in memory (small EXE) 216 if (coffSymbolTableOffset < blockLength) { 217 blockLength = coffSymbolTableOffset; 218 n = 0; 219 } else { 220 n = coffSymbolTableOffset - blockLength; 221 } 222 } else { 223 addsize = (int) (fs.Length & 7); 224 if (addsize > 0) 225 addsize = 8 - addsize; 226 227 n = fs.Length - blockLength; 228 } 229 230 // Authenticode(r) gymnastics 231 // Hash from (generally) 0 to 215 (216 bytes) 232 int pe = peOffset + 88; 233 hash.TransformBlock (fileblock, 0, pe, fileblock, 0); 234 // then skip 4 for checksum 235 pe += 4; 236 // Continue hashing from (generally) 220 to 279 (60 bytes) 237 hash.TransformBlock (fileblock, pe, 60, fileblock, pe); 238 // then skip 8 bytes for IMAGE_DIRECTORY_ENTRY_SECURITY 239 pe += 68; 240 241 // everything is present so start the hashing 242 if (n == 0) { 243 // hash the (only) block 244 hash.TransformFinalBlock (fileblock, pe, blockLength - pe); 245 } 246 else { 247 // hash the last part of the first (already in memory) block 248 hash.TransformBlock (fileblock, pe, blockLength - pe, fileblock, pe); 249 250 // hash by blocks of 4096 bytes 251 long blocks = (n >> 12); 252 int remainder = (int)(n - (blocks << 12)); 253 if (remainder == 0) { 254 blocks--; 255 remainder = 4096; 256 } 257 // blocks 258 while (blocks-- > 0) { 259 fs.Read (fileblock, 0, fileblock.Length); 260 hash.TransformBlock (fileblock, 0, fileblock.Length, fileblock, 0); 261 } 262 // remainder 263 if (fs.Read (fileblock, 0, remainder) != remainder) 264 return null; 265 266 if (addsize > 0) { 267 hash.TransformBlock (fileblock, 0, remainder, fileblock, 0); 268 hash.TransformFinalBlock (new byte [addsize], 0, addsize); 269 } else { 270 hash.TransformFinalBlock (fileblock, 0, remainder); 271 } 272 } 273 return hash.Hash; 274 } 275 276 // for compatibility only HashFile(string fileName, string hashName)277 protected byte[] HashFile (string fileName, string hashName) 278 { 279 try { 280 Open (fileName); 281 HashAlgorithm hash = HashAlgorithm.Create (hashName); 282 byte[] result = GetHash (hash); 283 Close (); 284 return result; 285 } 286 catch { 287 return null; 288 } 289 } 290 } 291 } 292