This commit was manufactured by cvs2svn to create branch 'mono-1-0'.
[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 // (C) 2004 Novell (http://www.novell.com)
9 //
10
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 // 
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 // 
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31
32 using System;
33 using System.Collections;
34 using System.Globalization;
35 using System.IO;
36 using System.Security.Cryptography;
37 using System.Text;
38 using System.Net;
39
40 using Mono.Security;
41 using Mono.Security.X509;
42
43 namespace Mono.Security.Authenticode {
44
45         public class AuthenticodeFormatter : AuthenticodeBase {
46
47                 private Authority authority;
48                 private X509CertificateCollection certs;
49                 private ArrayList crls;
50                 private string hash;
51                 private RSA rsa;
52                 private Uri timestamp;
53                 private ASN1 authenticode;
54                 private PKCS7.SignedData pkcs7;
55                 private string description;
56                 private Uri url;
57
58                 public AuthenticodeFormatter () : base () 
59                 {
60                         certs = new X509CertificateCollection ();
61                         crls = new ArrayList ();
62                         authority = Authority.Maximum;
63                         pkcs7 = new PKCS7.SignedData ();
64                 }
65
66                 public Authority Authority {
67                         get { return authority; }
68                         set { authority = value; }
69                 }
70
71                 public X509CertificateCollection Certificates {
72                         get { return certs; }
73                 }
74
75                 public ArrayList Crl {
76                         get { return crls; }
77                 }
78
79                 public string Hash {
80                         get { 
81                                 if (hash == null)
82                                         hash = "MD5";
83                                 return hash; 
84                         }
85                         set {
86                                 if (value == null)
87                                         throw new ArgumentNullException ("Hash");
88
89                                 string h = value.ToUpper (CultureInfo.InvariantCulture);
90                                 switch (h) {
91                                         case "MD5":
92                                         case "SHA1":
93                                                 hash = h;
94                                                 break;
95                                         default:
96                                                 throw new ArgumentException ("Invalid Authenticode hash algorithm");
97                                 }
98                         }
99                 }
100
101                 public RSA RSA {
102                         get { return rsa; }
103                         set { rsa = value; }
104                 }
105
106                 public Uri TimestampUrl {
107                         get { return timestamp; }
108                         set { timestamp = value; }
109                 }
110
111                 public string Description {
112                         get { return description; }
113                         set { description = value; }
114                 }
115
116                 public Uri Url {
117                         get { return url; }
118                         set { url = value; }
119                 }
120
121                 private ASN1 AlgorithmIdentifier (string oid) 
122                 {
123                         ASN1 ai = new ASN1 (0x30);
124                         ai.Add (ASN1Convert.FromOid (oid));
125                         ai.Add (new ASN1 (0x05));       // NULL
126                         return ai;
127                 }
128
129                 private ASN1 Attribute (string oid, ASN1 value)
130                 {
131                         ASN1 attr = new ASN1 (0x30);
132                         attr.Add (ASN1Convert.FromOid (oid));
133                         ASN1 aset = attr.Add (new ASN1 (0x31));
134                         aset.Add (value);
135                         return attr;
136                 }
137
138                 private ASN1 Opus (string description, string url) 
139                 {
140                         ASN1 opus = new ASN1 (0x30);
141                         if (description != null) {
142                                 ASN1 part1 = opus.Add (new ASN1 (0xA0));
143                                 part1.Add (new ASN1 (0x80, Encoding.BigEndianUnicode.GetBytes (description)));
144                         }
145                         if (url != null) {
146                                 ASN1 part2 = opus.Add (new ASN1 (0xA1));
147                                 part2.Add (new ASN1 (0x80, Encoding.ASCII.GetBytes (url)));
148                         }
149                         return opus;
150                 }
151
152                 // pkcs 1
153                 private const string rsaEncryption = "1.2.840.113549.1.1.1";
154                 // pkcs 7
155                 private const string data = "1.2.840.113549.1.7.1";
156                 private const string signedData = "1.2.840.113549.1.7.2";
157                 // pkcs 9
158                 private const string contentType = "1.2.840.113549.1.9.3";
159                 private const string messageDigest  = "1.2.840.113549.1.9.4";
160                 private const string countersignature = "1.2.840.113549.1.9.6";
161                 // microsoft spc (software publisher certificate)
162                 private const string spcStatementType = "1.3.6.1.4.1.311.2.1.11";
163                 private const string spcSpOpusInfo = "1.3.6.1.4.1.311.2.1.12";
164                 private const string spcPelmageData = "1.3.6.1.4.1.311.2.1.15";
165                 private const string individualCodeSigning = "1.3.6.1.4.1.311.2.1.21";
166                 private const string commercialCodeSigning = "1.3.6.1.4.1.311.2.1.22";
167                 private const string timestampCountersignature = "1.3.6.1.4.1.311.3.2.1";
168
169                 private static byte[] version = { 0x01 };
170                 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 };
171
172                 private byte[] Header (byte[] fileHash, string hashAlgorithm) 
173                 {
174                         string hashOid = CryptoConfig.MapNameToOID (hashAlgorithm);
175                         ASN1 content = new ASN1 (0x30);
176                         ASN1 c1 = content.Add (new ASN1 (0x30));
177                         c1.Add (ASN1Convert.FromOid (spcPelmageData));
178                         c1.Add (new ASN1 (0x30, obsolete));
179                         ASN1 c2 = content.Add (new ASN1 (0x30));
180                         c2.Add (AlgorithmIdentifier (hashOid));
181                         c2.Add (new ASN1 (0x04, fileHash));
182
183                         pkcs7.HashName = hashAlgorithm;
184                         pkcs7.Certificates.AddRange (certs);
185                         pkcs7.ContentInfo.ContentType = spcIndirectDataContext;
186                         pkcs7.ContentInfo.Content.Add (content);
187
188                         pkcs7.SignerInfo.Certificate = certs [0];
189                         pkcs7.SignerInfo.Key = rsa;
190                         pkcs7.SignerInfo.AuthenticatedAttributes.Add (Attribute (spcSpOpusInfo, Opus (description, url.ToString ())));
191                         pkcs7.SignerInfo.AuthenticatedAttributes.Add (Attribute (contentType, ASN1Convert.FromOid (spcIndirectDataContext)));
192                         pkcs7.SignerInfo.AuthenticatedAttributes.Add (Attribute (spcStatementType, new ASN1 (0x30, ASN1Convert.FromOid (commercialCodeSigning).GetBytes ())));
193
194                         ASN1 temp = pkcs7.ASN1; // sign
195                         return pkcs7.SignerInfo.Signature;
196                 }
197
198                 public ASN1 TimestampRequest (byte[] signature) 
199                 {
200                         PKCS7.ContentInfo ci = new PKCS7.ContentInfo (PKCS7.Oid.data);
201                         ci.Content.Add (new ASN1 (0x04, signature));
202                         return PKCS7.AlgorithmIdentifier (timestampCountersignature, ci.ASN1);
203                 }
204
205                 public void ProcessTimestamp (byte[] response)
206                 {
207                         ASN1 ts = new ASN1 (Convert.FromBase64String (Encoding.ASCII.GetString (response)));
208                         // first validate the received message
209                         // TODO
210
211                         // add the supplied certificates inside our signature
212                         for (int i=0; i < ts[1][0][3].Count; i++)
213                                 pkcs7.Certificates.Add (new X509Certificate (ts[1][0][3][i].GetBytes ()));
214
215                         // add an unauthentified attribute to our signature
216                         pkcs7.SignerInfo.UnauthenticatedAttributes.Add (Attribute (countersignature, ts[1][0][4][0]));
217                 }
218
219                 public bool Sign (string fileName) 
220                 {
221                         string hashAlgorithm = "MD5";
222                         FileStream fs = new FileStream (fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
223                         byte[] file = new byte [fs.Length];
224                         fs.Read (file, 0, file.Length);
225                         fs.Close ();
226
227                         // MZ - DOS header
228                         if (BitConverter.ToUInt16 (file, 0) != 0x5A4D)
229                                 return false;
230
231                         // find offset of PE header
232                         int peOffset = BitConverter.ToInt32 (file, 60);
233                         if (peOffset > file.Length)
234                                 return false;
235
236                         // PE - NT header
237                         if (BitConverter.ToUInt16 (file, peOffset) != 0x4550)
238                                 return false;
239
240                         // IMAGE_DIRECTORY_ENTRY_SECURITY
241                         int dirSecurityOffset = BitConverter.ToInt32 (file, peOffset + 152);
242                         int dirSecuritySize = BitConverter.ToInt32 (file, peOffset + 156);
243
244                         if (dirSecuritySize > 8) {
245                                 rawData = new byte [dirSecuritySize - 8];
246                                 Buffer.BlockCopy (file, dirSecurityOffset + 8, rawData, 0, rawData.Length);
247                         }
248                         else
249                                 rawData = null;
250
251                         HashAlgorithm hash = HashAlgorithm.Create (hashAlgorithm);
252                         // 0 to 215 (216) then skip 4 (checksum)
253                         int pe = peOffset + 88;
254                         hash.TransformBlock (file, 0, pe, file, 0);
255                         pe += 4;
256                         // 220 to 279 (60) then skip 8 (IMAGE_DIRECTORY_ENTRY_SECURITY)
257                         hash.TransformBlock (file, pe, 60, file, pe);
258                         pe += 68;
259                         // 288 to end of file
260                         int n = file.Length - pe;
261                         // minus any authenticode signature (with 8 bytes header)
262                         if (dirSecurityOffset != 0)
263                                 n -= (dirSecuritySize);
264                         hash.TransformFinalBlock (file, pe, n);
265
266                         //
267                         byte[] signature = Header (hash.Hash, hashAlgorithm);
268                         if (timestamp != null) {
269                                 ASN1 tsreq = TimestampRequest (signature);
270                                 WebClient wc = new WebClient ();
271                                 wc.Headers.Add ("Content-Type", "application/octet-stream");
272                                 wc.Headers.Add ("Accept", "application/octet-stream");
273                                 byte[] tsdata = Encoding.ASCII.GetBytes (Convert.ToBase64String (tsreq.GetBytes ()));
274                                 byte[] tsres = wc.UploadData (timestamp.ToString (), tsdata);
275                                 ProcessTimestamp (tsres);
276                         }
277                         PKCS7.ContentInfo sign = new PKCS7.ContentInfo (signedData);
278                         sign.Content.Add (pkcs7.ASN1);
279                         authenticode = sign.ASN1;
280
281                         // debug
282                         fs = File.Open (fileName + ".sig", FileMode.Create, FileAccess.Write);
283                         byte[] asn = authenticode.GetBytes ();
284                         fs.Write (asn, 0, asn.Length);
285                         fs.Close ();
286
287                         File.Copy (fileName, fileName + ".bak", true);
288
289                         fs = File.Open (fileName, FileMode.Create, FileAccess.Write);
290                         // IMAGE_DIRECTORY_ENTRY_SECURITY (offset, size)
291                         byte[] data = BitConverter.GetBytes (file.Length);
292                         file [peOffset + 152] = data [0];
293                         file [peOffset + 153] = data [1];
294                         file [peOffset + 154] = data [2];
295                         file [peOffset + 155] = data [3];
296                         int size = asn.Length + 8;
297                         // must be a multiple of 8 bytes
298                         int addsize = (size % 8);
299                         if (addsize > 0)
300                                 addsize = 8 - addsize;
301                         size += addsize;
302                         data = BitConverter.GetBytes (size);            // header
303                         file [peOffset + 156] = data [0];
304                         file [peOffset + 157] = data [1];
305                         file [peOffset + 158] = data [2];
306                         file [peOffset + 159] = data [3];
307                         fs.Write (file, 0, file.Length);
308                         fs.Write (data, 0, data.Length);                // length (again)
309                         data = BitConverter.GetBytes (0x00020200);      // magic
310                         fs.Write (data, 0, data.Length);
311                         fs.Write (asn, 0, asn.Length);
312                         // fill up
313                         byte[] fillup = new byte [addsize];
314                         fs.Write (fillup, 0, fillup.Length);
315                         fs.Close ();
316
317                         return true;
318                 }
319
320                 // in case we just want to timestamp the file
321                 public bool Timestamp (string fileName) 
322                 {
323                         return true;
324                 }
325         }
326 }