* roottypes.cs: Rename from tree.cs.
[mono.git] / mcs / class / Mono.Security / Mono.Security.Authenticode / AuthenticodeFormatter.cs
1 //
2 // AuthenticodeFormatter.cs: Authenticode signature generator
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.Collections;
32 using System.Globalization;
33 using System.IO;
34 using System.Security.Cryptography;
35 using System.Text;
36 using System.Net;
37
38 using Mono.Security;
39 using Mono.Security.X509;
40
41 namespace Mono.Security.Authenticode {
42
43         public class AuthenticodeFormatter : AuthenticodeBase {
44
45                 private Authority authority;
46                 private X509CertificateCollection certs;
47                 private ArrayList crls;
48                 private string hash;
49                 private RSA rsa;
50                 private Uri timestamp;
51                 private ASN1 authenticode;
52                 private PKCS7.SignedData pkcs7;
53                 private string description;
54                 private Uri url;
55                 private byte [] entry;
56
57                 public AuthenticodeFormatter () : base () 
58                 {
59                         certs = new X509CertificateCollection ();
60                         crls = new ArrayList ();
61                         authority = Authority.Maximum;
62                         pkcs7 = new PKCS7.SignedData ();
63                 }
64
65                 public Authority Authority {
66                         get { return authority; }
67                         set { authority = value; }
68                 }
69
70                 public X509CertificateCollection Certificates {
71                         get { return certs; }
72                 }
73
74                 public ArrayList Crl {
75                         get { return crls; }
76                 }
77
78                 public string Hash {
79                         get { 
80                                 if (hash == null)
81                                         hash = "MD5";
82                                 return hash; 
83                         }
84                         set {
85                                 if (value == null)
86                                         throw new ArgumentNullException ("Hash");
87
88                                 string h = value.ToUpper (CultureInfo.InvariantCulture);
89                                 switch (h) {
90                                         case "MD5":
91                                         case "SHA1":
92                                                 hash = h;
93                                                 break;
94                                         default:
95                                                 throw new ArgumentException ("Invalid Authenticode hash algorithm");
96                                 }
97                         }
98                 }
99
100                 public RSA RSA {
101                         get { return rsa; }
102                         set { rsa = value; }
103                 }
104
105                 public Uri TimestampUrl {
106                         get { return timestamp; }
107                         set { timestamp = value; }
108                 }
109
110                 public string Description {
111                         get { return description; }
112                         set { description = value; }
113                 }
114
115                 public Uri Url {
116                         get { return url; }
117                         set { url = value; }
118                 }
119
120                 private ASN1 AlgorithmIdentifier (string oid) 
121                 {
122                         ASN1 ai = new ASN1 (0x30);
123                         ai.Add (ASN1Convert.FromOid (oid));
124                         ai.Add (new ASN1 (0x05));       // NULL
125                         return ai;
126                 }
127
128                 private ASN1 Attribute (string oid, ASN1 value)
129                 {
130                         ASN1 attr = new ASN1 (0x30);
131                         attr.Add (ASN1Convert.FromOid (oid));
132                         ASN1 aset = attr.Add (new ASN1 (0x31));
133                         aset.Add (value);
134                         return attr;
135                 }
136
137                 private ASN1 Opus (string description, string url) 
138                 {
139                         ASN1 opus = new ASN1 (0x30);
140                         if (description != null) {
141                                 ASN1 part1 = opus.Add (new ASN1 (0xA0));
142                                 part1.Add (new ASN1 (0x80, Encoding.BigEndianUnicode.GetBytes (description)));
143                         }
144                         if (url != null) {
145                                 ASN1 part2 = opus.Add (new ASN1 (0xA1));
146                                 part2.Add (new ASN1 (0x80, Encoding.ASCII.GetBytes (url)));
147                         }
148                         return opus;
149                 }
150
151                 // pkcs 1
152 //              private const string rsaEncryption = "1.2.840.113549.1.1.1";
153                 // pkcs 7
154 //              private const string data = "1.2.840.113549.1.7.1";
155                 private const string signedData = "1.2.840.113549.1.7.2";
156                 // pkcs 9
157 //              private const string contentType = "1.2.840.113549.1.9.3";
158 //              private const string messageDigest  = "1.2.840.113549.1.9.4";
159                 private const string countersignature = "1.2.840.113549.1.9.6";
160                 // microsoft spc (software publisher certificate)
161                 private const string spcStatementType = "1.3.6.1.4.1.311.2.1.11";
162                 private const string spcSpOpusInfo = "1.3.6.1.4.1.311.2.1.12";
163                 private const string spcPelmageData = "1.3.6.1.4.1.311.2.1.15";
164 //              private const string individualCodeSigning = "1.3.6.1.4.1.311.2.1.21";
165                 private const string commercialCodeSigning = "1.3.6.1.4.1.311.2.1.22";
166                 private const string timestampCountersignature = "1.3.6.1.4.1.311.3.2.1";
167
168                 //private static byte[] version = { 0x01 };
169                 private static byte[] obsolete = { 0x03, 0x01, 0x00, 0xA0, 0x20, 0xA2, 0x1E, 0x80, 0x1C, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x4F, 0x00, 0x62, 0x00, 0x73, 0x00, 0x6F, 0x00, 0x6C, 0x00, 0x65, 0x00, 0x74, 0x00, 0x65, 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x3E };
170
171                 private byte[] Header (byte[] fileHash, string hashAlgorithm) 
172                 {
173                         string hashOid = CryptoConfig.MapNameToOID (hashAlgorithm);
174                         ASN1 content = new ASN1 (0x30);
175                         ASN1 c1 = content.Add (new ASN1 (0x30));
176                         c1.Add (ASN1Convert.FromOid (spcPelmageData));
177                         c1.Add (new ASN1 (0x30, obsolete));
178                         ASN1 c2 = content.Add (new ASN1 (0x30));
179                         c2.Add (AlgorithmIdentifier (hashOid));
180                         c2.Add (new ASN1 (0x04, fileHash));
181
182                         pkcs7.HashName = hashAlgorithm;
183                         pkcs7.Certificates.AddRange (certs);
184                         pkcs7.ContentInfo.ContentType = spcIndirectDataContext;
185                         pkcs7.ContentInfo.Content.Add (content);
186
187                         pkcs7.SignerInfo.Certificate = certs [0];
188                         pkcs7.SignerInfo.Key = rsa;
189
190                         ASN1 opus = null;
191                         if (url == null)
192                                 opus = Attribute (spcSpOpusInfo, Opus (description, null));
193                         else
194                                 opus = Attribute (spcSpOpusInfo, Opus (description, url.ToString ()));
195                         pkcs7.SignerInfo.AuthenticatedAttributes.Add (opus);
196                         pkcs7.SignerInfo.AuthenticatedAttributes.Add (Attribute (spcStatementType, new ASN1 (0x30, ASN1Convert.FromOid (commercialCodeSigning).GetBytes ())));
197                         pkcs7.GetASN1 (); // sign
198                         return pkcs7.SignerInfo.Signature;
199                 }
200
201                 public ASN1 TimestampRequest (byte[] signature) 
202                 {
203                         PKCS7.ContentInfo ci = new PKCS7.ContentInfo (PKCS7.Oid.data);
204                         ci.Content.Add (new ASN1 (0x04, signature));
205                         return PKCS7.AlgorithmIdentifier (timestampCountersignature, ci.ASN1);
206                 }
207
208                 public void ProcessTimestamp (byte[] response)
209                 {
210                         ASN1 ts = new ASN1 (Convert.FromBase64String (Encoding.ASCII.GetString (response)));
211                         // first validate the received message
212                         // TODO
213
214                         // add the supplied certificates inside our signature
215                         for (int i=0; i < ts[1][0][3].Count; i++)
216                                 pkcs7.Certificates.Add (new X509Certificate (ts[1][0][3][i].GetBytes ()));
217
218                         // add an unauthentified attribute to our signature
219                         pkcs7.SignerInfo.UnauthenticatedAttributes.Add (Attribute (countersignature, ts[1][0][4][0]));
220                 }
221
222                 private byte[] Timestamp (byte[] signature)
223                 {
224                         ASN1 tsreq = TimestampRequest (signature);
225                         WebClient wc = new WebClient ();
226                         wc.Headers.Add ("Content-Type", "application/octet-stream");
227                         wc.Headers.Add ("Accept", "application/octet-stream");
228                         byte[] tsdata = Encoding.ASCII.GetBytes (Convert.ToBase64String (tsreq.GetBytes ()));
229                         return wc.UploadData (timestamp.ToString (), tsdata);
230                 }
231
232                 private byte[] file;
233                 private int pe_offset = -1;
234                 private int sec_offset = -1;
235                 private int sec_size = -1;
236
237                 private int PEOffset {
238                         get {
239                                 if (file == null)
240                                         return -1;
241                                 if (pe_offset == -1) {
242                                         int peOffset = BitConverterLE.ToInt32 (file, 60);
243                                         if (peOffset < file.Length)
244                                                 pe_offset = peOffset;
245                                 }
246                                 return pe_offset;
247                         }
248                 }
249
250                 private int SecurityOffset {
251                         get {
252                                 if (file == null)
253                                         return -1;
254                                 if (sec_offset == -1) {
255                                         sec_offset = BitConverterLE.ToInt32 (file, PEOffset + 152);
256                                 }
257                                 return sec_offset;
258                         }
259                 }
260
261                 private int SecuritySize {
262                         get {
263                                 if (file == null)
264                                         return -1;
265                                 if (sec_size == -1) {
266                                         sec_size = BitConverterLE.ToInt32 (file, PEOffset + 156);
267                                 }
268                                 return sec_size;
269                         }
270                 }
271
272                 private bool Open (string fileName)
273                 {
274                         using (FileStream fs = new FileStream (fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) {
275                                 file = new byte[fs.Length];
276                                 fs.Read (file, 0, file.Length);
277                                 fs.Close ();
278                         }
279
280                         // MZ - DOS header
281                         if (BitConverterLE.ToUInt16 (file, 0) != 0x5A4D)
282                                 return false;
283
284                         // PE Header
285                         if (PEOffset == -1)
286                                 return false;
287
288                         // PE - NT header
289                         if (BitConverterLE.ToUInt16 (file, PEOffset) != 0x4550)
290                                 return false;
291
292                         // IMAGE_DIRECTORY_ENTRY_SECURITY
293                         if (SecuritySize > 8) {
294                                 entry = new byte[SecuritySize - 8];
295                                 Buffer.BlockCopy (file, SecurityOffset + 8, entry, 0, entry.Length);
296                         } else
297                                 entry = null;
298
299                         return true;
300                 }
301
302                 private bool Save (string fileName, byte[] asn)
303                 {
304 #if !DEBUG
305                         using (FileStream fs = File.Open (fileName + ".sig", FileMode.Create, FileAccess.Write)) {
306                                 fs.Write (asn, 0, asn.Length);
307                                 fs.Close ();
308                         }
309 #endif
310                         // someday I may be sure enough to move this into DEBUG ;-)
311                         File.Copy (fileName, fileName + ".bak", true);
312
313                         using (FileStream fs = File.Open (fileName, FileMode.Create, FileAccess.Write)) {
314                                 int filesize = (SecurityOffset == 0) ? file.Length : SecurityOffset;
315                                 // IMAGE_DIRECTORY_ENTRY_SECURITY (offset, size)
316                                 byte[] data = BitConverterLE.GetBytes (filesize);
317                                 file[PEOffset + 152] = data[0];
318                                 file[PEOffset + 153] = data[1];
319                                 file[PEOffset + 154] = data[2];
320                                 file[PEOffset + 155] = data[3];
321                                 int size = asn.Length + 8;
322                                 // must be a multiple of 8 bytes
323                                 int addsize = (size % 8);
324                                 if (addsize > 0)
325                                         addsize = 8 - addsize;
326                                 size += addsize;
327                                 data = BitConverterLE.GetBytes (size);          // header
328                                 file[PEOffset + 156] = data[0];
329                                 file[PEOffset + 157] = data[1];
330                                 file[PEOffset + 158] = data[2];
331                                 file[PEOffset + 159] = data[3];
332                                 fs.Write (file, 0, filesize);
333                                 fs.Write (data, 0, data.Length);                // length (again)
334                                 data = BitConverterLE.GetBytes (0x00020200);    // magic
335                                 fs.Write (data, 0, data.Length);
336                                 fs.Write (asn, 0, asn.Length);
337                                 // fill up
338                                 byte[] fillup = new byte[addsize];
339                                 fs.Write (fillup, 0, fillup.Length);
340                                 fs.Close ();
341                         }
342                         return true;
343                 }
344
345                 public bool Sign (string fileName) 
346                 {
347                         try {
348                                 if (!Open (fileName))
349                                         return false;
350
351                                 HashAlgorithm hash = HashAlgorithm.Create (Hash);
352                                 // 0 to 215 (216) then skip 4 (checksum)
353                                 int pe = PEOffset + 88;
354                                 hash.TransformBlock (file, 0, pe, file, 0);
355                                 pe += 4;
356                                 // 220 to 279 (60) then skip 8 (IMAGE_DIRECTORY_ENTRY_SECURITY)
357                                 hash.TransformBlock (file, pe, 60, file, pe);
358                                 pe += 68;
359                                 // 288 to end of file
360                                 int n = file.Length - pe;
361                                 // minus any authenticode signature (with 8 bytes header)
362                                 if (SecurityOffset != 0)
363                                         n -= (SecuritySize);
364                                 hash.TransformFinalBlock (file, pe, n);
365
366                                 byte[] signature = Header (hash.Hash, Hash);
367                                 if (timestamp != null) {
368                                         byte[] ts = Timestamp (signature);
369                                         // add timestamp information inside the current pkcs7 SignedData instance
370                                         // (this is possible because the data isn't yet signed)
371                                         ProcessTimestamp (ts);
372                                 }
373
374                                 PKCS7.ContentInfo sign = new PKCS7.ContentInfo (signedData);
375                                 sign.Content.Add (pkcs7.ASN1);
376                                 authenticode = sign.ASN1;
377
378                                 return Save (fileName, authenticode.GetBytes ());
379                         }
380                         catch (Exception e) {
381                                 Console.WriteLine (e);
382                         }
383                         return false;
384                 }
385
386                 // in case we just want to timestamp the file
387                 public bool Timestamp (string fileName) 
388                 {
389                         try {
390                                 AuthenticodeDeformatter def = new AuthenticodeDeformatter (fileName);
391                                 byte[] signature = def.Signature;
392                                 if ((signature != null) && Open (fileName)) {
393                                         PKCS7.ContentInfo ci = new PKCS7.ContentInfo (signature);
394                                         pkcs7 = new PKCS7.SignedData (ci.Content);
395
396                                         byte[] response = Timestamp (pkcs7.SignerInfo.Signature);
397                                         ASN1 ts = new ASN1 (Convert.FromBase64String (Encoding.ASCII.GetString (response)));
398                                         // insert new certificates and countersignature into the original signature
399                                         ASN1 asn = new ASN1 (signature);
400                                         ASN1 content = asn.Element (1, 0xA0);
401                                         if (content == null)
402                                                 return false;
403
404                                         ASN1 signedData = content.Element (0, 0x30);
405                                         if (signedData == null)
406                                                 return false;
407
408                                         // add the supplied certificates inside our signature
409                                         ASN1 certificates = signedData.Element (3, 0xA0);
410                                         if (certificates == null) {
411                                                 certificates = new ASN1 (0xA0);
412                                                 signedData.Add (certificates);
413                                         }
414                                         for (int i = 0; i < ts[1][0][3].Count; i++) {
415                                                 certificates.Add (ts[1][0][3][i]);
416                                         }
417
418                                         // add an unauthentified attribute to our signature
419                                         ASN1 signerInfoSet = signedData[signedData.Count - 1];
420                                         ASN1 signerInfo = signerInfoSet[0];
421                                         ASN1 unauthenticated = signerInfo[signerInfo.Count - 1];
422                                         if (unauthenticated.Tag != 0xA1) {
423                                                 unauthenticated = new ASN1 (0xA1);
424                                                 signerInfo.Add (unauthenticated);
425                                         }
426                                         unauthenticated.Add (Attribute (countersignature, ts[1][0][4][0]));
427
428                                         return Save (fileName, asn.GetBytes ());
429                                 }
430                         }
431                         catch (Exception e) {
432                                 Console.WriteLine (e);
433                         }
434                         return false;
435                 }
436         }
437 }