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