New test.
[mono.git] / mcs / class / corlib / Mono.Security.Authenticode / AuthenticodeBase.cs
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 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
67                 public AuthenticodeBase ()
68                 {
69                         fileblock = new byte [4096];
70                 }
71
72                 internal void Open (string filename)
73                 {
74                         if (fs != null)
75                                 Close ();
76                         fs = new FileStream (filename, FileMode.Open, FileAccess.Read, FileShare.Read);
77                 }
78
79                 internal void Close ()
80                 {
81                         if (fs != null) {
82                                 fs.Close ();
83                                 fs = null;
84                                 blockNo = 0;
85                         }
86                 }
87
88                 internal bool ReadFirstBlock ()
89                 {
90                         if (fs == null)
91                                 return false;
92
93                         fs.Position = 0;
94                         // read first block - it will include (100% sure) 
95                         // the MZ header and (99.9% sure) the PE header
96                         blockLength = fs.Read (fileblock, 0, fileblock.Length);
97                         blockNo = 1;
98                         if (blockLength < 64)
99                                 return false;   // invalid PE file
100
101                         // 1. Validate the MZ header informations
102                         // 1.1. Check for magic MZ at start of header
103                         if (BitConverterLE.ToUInt16 (fileblock, 0) != 0x5A4D)
104                                 return false;
105
106                         // 1.2. Find the offset of the PE header
107                         peOffset = BitConverterLE.ToInt32 (fileblock, 60);
108                         if (peOffset > fileblock.Length) {
109                                 // just in case (0.1%) this can actually happen
110                                 string msg = String.Format (Locale.GetText (
111                                         "Header size too big (> {0} bytes)."),
112                                         fileblock.Length);
113                                 throw new NotSupportedException (msg);
114                         }
115                         if (peOffset > fs.Length)
116                                 return false;
117
118                         // 2. Read between DOS header and first part of PE header
119                         // 2.1. Check for magic PE at start of header
120                         if (BitConverterLE.ToUInt16 (fileblock, peOffset) != 0x4550)
121                                 return false;
122
123                         // 2.2. Locate IMAGE_DIRECTORY_ENTRY_SECURITY (offset and size)
124                         dirSecurityOffset = BitConverterLE.ToInt32 (fileblock, peOffset + 152);
125                         dirSecuritySize = BitConverterLE.ToInt32 (fileblock, peOffset + 156);
126
127                         return true;
128                 }
129
130                 internal byte[] GetSecurityEntry () 
131                 {
132                         if (blockNo < 1)
133                                 ReadFirstBlock ();
134
135                         if (dirSecuritySize > 8) {
136                                 // remove header from size (not ASN.1 based)
137                                 byte[] secEntry = new byte [dirSecuritySize - 8];
138                                 // position after header and read entry
139                                 fs.Position = dirSecurityOffset + 8;
140                                 fs.Read (secEntry, 0, secEntry.Length);
141                                 return secEntry;
142                         }
143                         return null;
144                 }
145
146                 // returns null if the file isn't signed
147                 internal byte[] GetHash (HashAlgorithm hash)
148                 {
149                         if (blockNo < 1)
150                                 ReadFirstBlock ();
151                         fs.Position = blockLength;
152
153                         // hash the rest of the file
154                         long n = fs.Length - blockLength;
155                         // minus any authenticode signature (with 8 bytes header)
156                         if (dirSecurityOffset > 0) {
157                                 // it is also possible that the signature block 
158                                 // starts within the block in memory (small EXE)
159                                 if (dirSecurityOffset < blockLength) {
160                                         blockLength = dirSecurityOffset;
161                                         n = 0;
162                                 }
163                                 else
164                                         n -= (dirSecuritySize);
165                         }
166
167                         // Authenticode(r) gymnastics
168                         // Hash from (generally) 0 to 215 (216 bytes)
169                         int pe = peOffset + 88;
170                         hash.TransformBlock (fileblock, 0, pe, fileblock, 0);
171                         // then skip 4 for checksum
172                         pe += 4;
173                         // Continue hashing from (generally) 220 to 279 (60 bytes)
174                         hash.TransformBlock (fileblock, pe, 60, fileblock, pe);
175                         // then skip 8 bytes for IMAGE_DIRECTORY_ENTRY_SECURITY
176                         pe += 68;
177
178                         // everything is present so start the hashing
179                         if (n == 0) {
180                                 // hash the (only) block
181                                 hash.TransformFinalBlock (fileblock, pe, blockLength - pe);
182                         }
183                         else {
184                                 // hash the last part of the first (already in memory) block
185                                 hash.TransformBlock (fileblock, pe, blockLength - pe, fileblock, pe);
186
187                                 // hash by blocks of 4096 bytes
188                                 long blocks = (n >> 12);
189                                 int remainder = (int)(n - (blocks << 12));
190                                 if (remainder == 0) {
191                                         blocks--;
192                                         remainder = 4096;
193                                 }
194                                 // blocks
195                                 while (blocks-- > 0) {
196                                         fs.Read (fileblock, 0, fileblock.Length);
197                                         hash.TransformBlock (fileblock, 0, fileblock.Length, fileblock, 0);
198                                 }
199                                 // remainder
200                                 if (fs.Read (fileblock, 0, remainder) != remainder)
201                                         return null;
202                                 hash.TransformFinalBlock (fileblock, 0, remainder);
203                         }
204                         return hash.Hash;
205                 }
206
207                 // for compatibility only
208                 protected byte[] HashFile (string fileName, string hashName) 
209                 {
210                         try {
211                                 Open (fileName);
212                                 HashAlgorithm hash = HashAlgorithm.Create (hashName);
213                                 byte[] result = GetHash (hash);
214                                 Close ();
215                                 return result;
216                         }
217                         catch {
218                                 return null;
219                         }
220                 }
221         }
222 }