Merge pull request #2366 from xmcclure/scripts-babysitter
[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, 2006-2007 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
56                 public AuthenticodeFormatter () : base () 
57                 {
58                         certs = new X509CertificateCollection ();
59                         crls = new ArrayList ();
60                         authority = Authority.Maximum;
61                         pkcs7 = new PKCS7.SignedData ();
62                 }
63
64                 public Authority Authority {
65                         get { return authority; }
66                         set { authority = value; }
67                 }
68
69                 public X509CertificateCollection Certificates {
70                         get { return certs; }
71                 }
72
73                 public ArrayList Crl {
74                         get { return crls; }
75                 }
76
77                 public string Hash {
78                         get { 
79                                 if (hash == null)
80                                         hash = "SHA1";
81                                 return hash; 
82                         }
83                         set {
84                                 if (value == null)
85                                         throw new ArgumentNullException ("Hash");
86
87                                 string h = value.ToUpper (CultureInfo.InvariantCulture);
88                                 switch (h) {
89                                         case "MD5":
90                                         case "SHA1":
91                                                 hash = h;
92                                                 break;
93                                         default:
94                                                 throw new ArgumentException ("Invalid Authenticode hash algorithm");
95                                 }
96                         }
97                 }
98
99                 public RSA RSA {
100                         get { return rsa; }
101                         set { rsa = value; }
102                 }
103
104                 public Uri TimestampUrl {
105                         get { return timestamp; }
106                         set { timestamp = value; }
107                 }
108
109                 public string Description {
110                         get { return description; }
111                         set { description = value; }
112                 }
113
114                 public Uri Url {
115                         get { return url; }
116                         set { url = value; }
117                 }
118
119                 private ASN1 AlgorithmIdentifier (string oid) 
120                 {
121                         ASN1 ai = new ASN1 (0x30);
122                         ai.Add (ASN1Convert.FromOid (oid));
123                         ai.Add (new ASN1 (0x05));       // NULL
124                         return ai;
125                 }
126
127                 private ASN1 Attribute (string oid, ASN1 value)
128                 {
129                         ASN1 attr = new ASN1 (0x30);
130                         attr.Add (ASN1Convert.FromOid (oid));
131                         ASN1 aset = attr.Add (new ASN1 (0x31));
132                         aset.Add (value);
133                         return attr;
134                 }
135
136                 private ASN1 Opus (string description, string url) 
137                 {
138                         ASN1 opus = new ASN1 (0x30);
139                         if (description != null) {
140                                 ASN1 part1 = opus.Add (new ASN1 (0xA0));
141                                 part1.Add (new ASN1 (0x80, Encoding.BigEndianUnicode.GetBytes (description)));
142                         }
143                         if (url != null) {
144                                 ASN1 part2 = opus.Add (new ASN1 (0xA1));
145                                 part2.Add (new ASN1 (0x80, Encoding.ASCII.GetBytes (url)));
146                         }
147                         return opus;
148                 }
149
150                 // pkcs 1
151 //              private const string rsaEncryption = "1.2.840.113549.1.1.1";
152                 // pkcs 7
153 //              private const string data = "1.2.840.113549.1.7.1";
154                 private const string signedData = "1.2.840.113549.1.7.2";
155                 // pkcs 9
156 //              private const string contentType = "1.2.840.113549.1.9.3";
157 //              private const string messageDigest  = "1.2.840.113549.1.9.4";
158                 private const string countersignature = "1.2.840.113549.1.9.6";
159                 // microsoft spc (software publisher certificate)
160                 private const string spcStatementType = "1.3.6.1.4.1.311.2.1.11";
161                 private const string spcSpOpusInfo = "1.3.6.1.4.1.311.2.1.12";
162                 private const string spcPelmageData = "1.3.6.1.4.1.311.2.1.15";
163 //              private const string individualCodeSigning = "1.3.6.1.4.1.311.2.1.21";
164                 private const string commercialCodeSigning = "1.3.6.1.4.1.311.2.1.22";
165                 private const string timestampCountersignature = "1.3.6.1.4.1.311.3.2.1";
166
167                 //private static byte[] version = { 0x01 };
168                 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 };
169
170                 private byte[] Header (byte[] fileHash, string hashAlgorithm) 
171                 {
172                         string hashOid = CryptoConfig.MapNameToOID (hashAlgorithm);
173                         ASN1 content = new ASN1 (0x30);
174                         ASN1 c1 = content.Add (new ASN1 (0x30));
175                         c1.Add (ASN1Convert.FromOid (spcPelmageData));
176                         c1.Add (new ASN1 (0x30, obsolete));
177                         ASN1 c2 = content.Add (new ASN1 (0x30));
178                         c2.Add (AlgorithmIdentifier (hashOid));
179                         c2.Add (new ASN1 (0x04, fileHash));
180
181                         pkcs7.HashName = hashAlgorithm;
182                         pkcs7.Certificates.AddRange (certs);
183                         pkcs7.ContentInfo.ContentType = spcIndirectDataContext;
184                         pkcs7.ContentInfo.Content.Add (content);
185
186                         pkcs7.SignerInfo.Certificate = certs [0];
187                         pkcs7.SignerInfo.Key = rsa;
188
189                         ASN1 opus = null;
190                         if (url == null)
191                                 opus = Attribute (spcSpOpusInfo, Opus (description, null));
192                         else
193                                 opus = Attribute (spcSpOpusInfo, Opus (description, url.ToString ()));
194                         pkcs7.SignerInfo.AuthenticatedAttributes.Add (opus);
195 // When using the MS Root Agency (test) we can't include this attribute in the signature or it won't validate!
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 bool Save (string fileName, byte[] asn)
233                 {
234 #if DEBUG
235                         using (FileStream fs = File.Open (fileName + ".sig", FileMode.Create, FileAccess.Write)) {
236                                 fs.Write (asn, 0, asn.Length);
237                                 fs.Close ();
238                         }
239 #endif
240                         // someday I may be sure enough to move this into DEBUG ;-)
241                         File.Copy (fileName, fileName + ".bak", true);
242
243                         using (FileStream fs = File.Open (fileName, FileMode.Open, FileAccess.ReadWrite)) {
244                                 int filesize;
245                                 if (SecurityOffset > 0) {
246                                         // file was already signed, we'll reuse the position for the updated signature
247                                         filesize = SecurityOffset;
248                                 } else if (CoffSymbolTableOffset > 0) {
249                                         // strip (deprecated) COFF symbol table
250                                         fs.Seek (PEOffset + 12, SeekOrigin.Begin);
251                                         for (int i = 0; i < 8; i++)
252                                                 fs.WriteByte (0);
253                                         // we'll put the Authenticode signature at this same place (just after the last section)
254                                         filesize = CoffSymbolTableOffset;
255                                 } else {
256                                         // file was never signed, nor does it contains (deprecated) COFF symbols
257                                         filesize = (int)fs.Length;
258                                 }
259                                 // must be a multiple of 8 bytes
260                                 int addsize = (filesize & 7);
261                                 if (addsize > 0)
262                                         addsize = 8 - addsize;
263
264                                 // IMAGE_DIRECTORY_ENTRY_SECURITY (offset, size)
265                                 byte[] data = BitConverterLE.GetBytes (filesize + addsize);
266                                 fs.Seek (PEOffset + 152, SeekOrigin.Begin);
267                                 fs.Write (data, 0, 4);
268                                 int size = asn.Length + 8;
269                                 int addsize_signature = (size & 7);
270                                 if (addsize_signature > 0)
271                                         addsize_signature = 8 - addsize_signature;
272                                 data = BitConverterLE.GetBytes (size + addsize_signature);
273                                 fs.Seek (PEOffset + 156, SeekOrigin.Begin);
274                                 fs.Write (data, 0, 4);
275                                 fs.Seek (filesize, SeekOrigin.Begin);
276                                 // align certificate entry to a multiple of 8 bytes
277                                 if (addsize > 0) {
278                                         byte[] fillup = new byte[addsize];
279                                         fs.Write (fillup, 0, fillup.Length);
280                                 }
281                                 fs.Write (data, 0, data.Length);                // length (again)
282                                 data = BitConverterLE.GetBytes (0x00020200);    // magic
283                                 fs.Write (data, 0, data.Length);
284                                 fs.Write (asn, 0, asn.Length);
285                                 if (addsize_signature > 0) {
286                                         byte[] fillup = new byte[addsize_signature];
287                                         fs.Write (fillup, 0, fillup.Length);
288                                 }
289                                 fs.Close ();
290                         }
291                         return true;
292                 }
293
294                 public bool Sign (string fileName) 
295                 {
296                         try {
297                                 Open (fileName);
298
299                                 HashAlgorithm hash = HashAlgorithm.Create (Hash);
300                                 // 0 to 215 (216) then skip 4 (checksum)
301
302                                 byte[] digest = GetHash (hash);
303                                 byte[] signature = Header (digest, Hash);
304                                 if (timestamp != null) {
305                                         byte[] ts = Timestamp (signature);
306                                         // add timestamp information inside the current pkcs7 SignedData instance
307                                         // (this is possible because the data isn't yet signed)
308                                         ProcessTimestamp (ts);
309                                 }
310
311                                 PKCS7.ContentInfo sign = new PKCS7.ContentInfo (signedData);
312                                 sign.Content.Add (pkcs7.ASN1);
313                                 authenticode = sign.ASN1;
314                                 Close ();
315
316                                 return Save (fileName, authenticode.GetBytes ());
317                         }
318                         catch (Exception e) {
319                                 Console.WriteLine (e);
320                         }
321                         return false;
322                 }
323
324                 // in case we just want to timestamp the file
325                 public bool Timestamp (string fileName) 
326                 {
327                         try {
328                                 AuthenticodeDeformatter def = new AuthenticodeDeformatter (fileName);
329                                 byte[] signature = def.Signature;
330                                 if (signature != null) {
331                                         Open (fileName);
332                                         PKCS7.ContentInfo ci = new PKCS7.ContentInfo (signature);
333                                         pkcs7 = new PKCS7.SignedData (ci.Content);
334
335                                         byte[] response = Timestamp (pkcs7.SignerInfo.Signature);
336                                         ASN1 ts = new ASN1 (Convert.FromBase64String (Encoding.ASCII.GetString (response)));
337                                         // insert new certificates and countersignature into the original signature
338                                         ASN1 asn = new ASN1 (signature);
339                                         ASN1 content = asn.Element (1, 0xA0);
340                                         if (content == null)
341                                                 return false;
342
343                                         ASN1 signedData = content.Element (0, 0x30);
344                                         if (signedData == null)
345                                                 return false;
346
347                                         // add the supplied certificates inside our signature
348                                         ASN1 certificates = signedData.Element (3, 0xA0);
349                                         if (certificates == null) {
350                                                 certificates = new ASN1 (0xA0);
351                                                 signedData.Add (certificates);
352                                         }
353                                         for (int i = 0; i < ts[1][0][3].Count; i++) {
354                                                 certificates.Add (ts[1][0][3][i]);
355                                         }
356
357                                         // add an unauthentified attribute to our signature
358                                         ASN1 signerInfoSet = signedData[signedData.Count - 1];
359                                         ASN1 signerInfo = signerInfoSet[0];
360                                         ASN1 unauthenticated = signerInfo[signerInfo.Count - 1];
361                                         if (unauthenticated.Tag != 0xA1) {
362                                                 unauthenticated = new ASN1 (0xA1);
363                                                 signerInfo.Add (unauthenticated);
364                                         }
365                                         unauthenticated.Add (Attribute (countersignature, ts[1][0][4][0]));
366
367                                         return Save (fileName, asn.GetBytes ());
368                                 }
369                         }
370                         catch (Exception e) {
371                                 Console.WriteLine (e);
372                         }
373                         return false;
374                 }
375         }
376 }