2003-09-01 Sebastien Pouliot <spouliot@videotron.ca>
[mono.git] / mcs / class / Mono.Security / Mono.Security.Authenticode / AuthenticodeFormatter.cs
1 //
2 // AuthenticodeFormatter.cs: Authenticode signature generator
3 //
4 // Author:
5 //      Sebastien Pouliot (spouliot@motus.com)
6 //
7 // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
8 //
9
10 using System;
11 using System.Collections;
12 using System.IO;
13 using System.Security.Cryptography;
14 //using System.Security.Cryptography.X509Certificates;
15 using System.Text;
16 using System.Net;
17
18 using Mono.Security;
19 using Mono.Security.X509;
20
21 namespace Mono.Security.Authenticode {
22
23         public class AuthenticodeFormatter : AuthenticodeBase {
24
25                 private Authority authority;
26                 private X509CertificateCollection certs;
27                 private ArrayList crls;
28                 private string hash;
29                 private RSA rsa;
30                 private string timestamp;
31                 private ASN1 authenticode;
32                 private PKCS7.SignedData pkcs7;
33                 private string description;
34                 private string url;
35
36                 public AuthenticodeFormatter () : base () 
37                 {
38                         certs = new X509CertificateCollection ();
39                         crls = new ArrayList ();
40                         authority = Authority.Maximum;
41                         pkcs7 = new PKCS7.SignedData ();
42                 }
43
44                 public Authority Authority {
45                         get { return authority; }
46                         set { authority = value; }
47                 }
48
49                 public X509CertificateCollection Certificates {
50                         get { return certs; }
51                 }
52
53                 public ArrayList CRL {
54                         get { return crls; }
55                 }
56
57                 public string Hash {
58                         get { 
59                                 if (hash == null)
60                                         hash = "MD5";
61                                 return hash; 
62                         }
63                         set {
64                                 string h = value.ToUpper ();
65                                 if ((h == "MD5") || (hash == "SHA1"))
66                                         hash = value;
67                                 else
68                                         throw new ArgumentException ("Invalid Authenticode hash algorithm");
69                         }
70                 }
71
72                 public RSA RSA {
73                         get { return rsa; }
74                         set { rsa = value; }
75                 }
76
77                 public string TimestampURL {
78                         get { return timestamp; }
79                         set { timestamp = value; } // URL
80                 }
81
82                 public string Description {
83                         get { return description; }
84                         set { description = value; }
85                 }
86
87                 public string URL {
88                         get { return url; }
89                         set { url = value; } // URL
90                 }
91
92                 private ASN1 AlgorithmIdentifier (string oid) 
93                 {
94                         ASN1 ai = new ASN1 (0x30);
95                         ai.Add (ASN1Convert.FromOID (oid));
96                         ai.Add (new ASN1 (0x05));       // NULL
97                         return ai;
98                 }
99
100                 private ASN1 Attribute (string oid, ASN1 value)
101                 {
102                         ASN1 attr = new ASN1 (0x30);
103                         attr.Add (ASN1Convert.FromOID (oid));
104                         ASN1 aset = attr.Add (new ASN1 (0x31));
105                         aset.Add (value);
106                         return attr;
107                 }
108
109                 private ASN1 Opus (string description, string url) 
110                 {
111                         ASN1 opus = new ASN1 (0x30);
112                         if (description != null) {
113                                 ASN1 part1 = opus.Add (new ASN1 (0xA0));
114                                 part1.Add (new ASN1 (0x80, Encoding.BigEndianUnicode.GetBytes (description)));
115                         }
116                         if (url != null) {
117                                 ASN1 part2 = opus.Add (new ASN1 (0xA1));
118                                 part2.Add (new ASN1 (0x80, Encoding.ASCII.GetBytes (url)));
119                         }
120                         return opus;
121                 }
122
123                 // pkcs 1
124                 private const string rsaEncryption = "1.2.840.113549.1.1.1";
125                 // pkcs 7
126                 private const string data = "1.2.840.113549.1.7.1";
127                 private const string signedData = "1.2.840.113549.1.7.2";
128                 // pkcs 9
129                 private const string contentType = "1.2.840.113549.1.9.3";
130                 private const string messageDigest  = "1.2.840.113549.1.9.4";
131                 private const string countersignature = "1.2.840.113549.1.9.6";
132                 // microsoft spc (software publisher certificate)
133                 private const string spcStatementType = "1.3.6.1.4.1.311.2.1.11";
134                 private const string spcSpOpusInfo = "1.3.6.1.4.1.311.2.1.12";
135                 private const string spcPelmageData = "1.3.6.1.4.1.311.2.1.15";
136                 private const string individualCodeSigning = "1.3.6.1.4.1.311.2.1.21";
137                 private const string commercialCodeSigning = "1.3.6.1.4.1.311.2.1.22";
138                 private const string timestampCountersignature = "1.3.6.1.4.1.311.3.2.1";
139
140                 private static byte[] version = { 0x01 };
141                 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 };
142
143                 private byte[] Header (byte[] fileHash, string hashAlgorithm) 
144                 {
145                         string hashOid = CryptoConfig.MapNameToOID (hashAlgorithm);
146                         ASN1 content = new ASN1 (0x30);
147                         ASN1 c1 = content.Add (new ASN1 (0x30));
148                         c1.Add (ASN1Convert.FromOID (spcPelmageData));
149                         c1.Add (new ASN1 (0x30, obsolete));
150                         ASN1 c2 = content.Add (new ASN1 (0x30));
151                         c2.Add (AlgorithmIdentifier (hashOid));
152                         c2.Add (new ASN1 (0x04, fileHash));
153
154                         pkcs7.HashName = hashAlgorithm;
155                         pkcs7.Certificates.AddRange (certs);
156                         pkcs7.ContentInfo.ContentType = spcIndirectDataContext;
157                         pkcs7.ContentInfo.Content.Add (content);
158
159                         pkcs7.SignerInfo.Certificate = certs [0];
160                         pkcs7.SignerInfo.Key = rsa;
161                         pkcs7.SignerInfo.AuthenticatedAttributes.Add (Attribute (spcSpOpusInfo, Opus (description, url)));
162                         pkcs7.SignerInfo.AuthenticatedAttributes.Add (Attribute (contentType, ASN1Convert.FromOID (spcIndirectDataContext)));
163                         pkcs7.SignerInfo.AuthenticatedAttributes.Add (Attribute (spcStatementType, new ASN1 (0x30, ASN1Convert.FromOID (commercialCodeSigning).GetBytes ())));
164
165                         ASN1 temp = pkcs7.ASN1; // sign
166                         return pkcs7.SignerInfo.Signature;
167                 }
168
169                 public ASN1 TimestampRequest (byte[] signature) 
170                 {
171                         PKCS7.ContentInfo ci = new PKCS7.ContentInfo (PKCS7.data);
172                         ci.Content.Add (new ASN1 (0x04, signature));
173                         return PKCS7.AlgorithmIdentifier (timestampCountersignature, ci.ASN1);
174                 }
175
176                 public void ProcessTimestamp (byte[] tsres) 
177                 {
178                         ASN1 ts = new ASN1 (Convert.FromBase64String (Encoding.ASCII.GetString (tsres)));
179                         // first validate the received message
180                         // TODO
181
182                         // add the supplied certificates inside our signature
183                         for (int i=0; i < ts[1][0][3].Count; i++)
184                                 pkcs7.Certificates.Add (new X509Certificate (ts[1][0][3][i].GetBytes ()));
185
186                         // add an unauthentified attribute to our signature
187                         pkcs7.SignerInfo.UnauthenticatedAttributes.Add (Attribute (countersignature, ts[1][0][4][0]));
188                 }
189
190                 public bool Sign (string fileName) 
191                 {
192                         string hashAlgorithm = "MD5";
193                         FileStream fs = new FileStream (fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
194                         byte[] file = new byte [fs.Length];
195                         fs.Read (file, 0, file.Length);
196                         fs.Close ();
197
198                         // MZ - DOS header
199                         if (BitConverter.ToUInt16 (file, 0) != 0x5A4D)
200                                 return false;
201
202                         // find offset of PE header
203                         int peOffset = BitConverter.ToInt32 (file, 60);
204                         if (peOffset > file.Length)
205                                 return false;
206
207                         // PE - NT header
208                         if (BitConverter.ToUInt16 (file, peOffset) != 0x4550)
209                                 return false;
210
211                         // IMAGE_DIRECTORY_ENTRY_SECURITY
212                         int dirSecurityOffset = BitConverter.ToInt32 (file, peOffset + 152);
213                         int dirSecuritySize = BitConverter.ToInt32 (file, peOffset + 156);
214
215                         if (dirSecuritySize > 8) {
216                                 rawData = new byte [dirSecuritySize - 8];
217                                 Array.Copy (file, dirSecurityOffset + 8, rawData, 0, rawData.Length);
218                         }
219                         else
220                                 rawData = null;
221
222                         HashAlgorithm hash = HashAlgorithm.Create (hashAlgorithm);
223                         // 0 to 215 (216) then skip 4 (checksum)
224                         int pe = peOffset + 88;
225                         hash.TransformBlock (file, 0, pe, file, 0);
226                         pe += 4;
227                         // 220 to 279 (60) then skip 8 (IMAGE_DIRECTORY_ENTRY_SECURITY)
228                         hash.TransformBlock (file, pe, 60, file, pe);
229                         pe += 68;
230                         // 288 to end of file
231                         int n = file.Length - pe;
232                         // minus any authenticode signature (with 8 bytes header)
233                         if (dirSecurityOffset != 0)
234                                 n -= (dirSecuritySize);
235                         hash.TransformFinalBlock (file, pe, n);
236
237                         //
238                         byte[] signature = Header (hash.Hash, hashAlgorithm);
239                         if (timestamp != null) {
240                                 ASN1 tsreq = TimestampRequest (signature);
241                                 WebClient wc = new WebClient ();
242                                 wc.Headers.Add ("Content-Type", "application/octet-stream");
243                                 wc.Headers.Add ("Accept", "application/octet-stream");
244                                 byte[] tsdata = Encoding.ASCII.GetBytes (Convert.ToBase64String (tsreq.GetBytes ()));
245                                 byte[] tsres = wc.UploadData (timestamp, tsdata);
246                                 ProcessTimestamp (tsres);
247                         }
248                         PKCS7.ContentInfo sign = new PKCS7.ContentInfo (signedData);
249                         sign.Content.Add (pkcs7.ASN1);
250                         authenticode = sign.ASN1;
251
252                         // debug
253                         fs = File.Open (fileName + ".sig", FileMode.Create, FileAccess.Write);
254                         byte[] asn = authenticode.GetBytes ();
255                         fs.Write (asn, 0, asn.Length);
256                         fs.Close ();
257
258                         File.Copy (fileName, fileName + ".bak", true);
259
260                         fs = File.Open (fileName, FileMode.Create, FileAccess.Write);
261                         // IMAGE_DIRECTORY_ENTRY_SECURITY (offset, size)
262                         byte[] data = BitConverter.GetBytes (file.Length);
263                         file [peOffset + 152] = data [0];
264                         file [peOffset + 153] = data [1];
265                         file [peOffset + 154] = data [2];
266                         file [peOffset + 155] = data [3];
267                         int size = asn.Length + 8;
268                         // must be a multiple of 8 bytes
269                         int addsize = (size % 8);
270                         if (addsize > 0)
271                                 addsize = 8 - addsize;
272                         size += addsize;
273                         data = BitConverter.GetBytes (size);            // header
274                         file [peOffset + 156] = data [0];
275                         file [peOffset + 157] = data [1];
276                         file [peOffset + 158] = data [2];
277                         file [peOffset + 159] = data [3];
278                         fs.Write (file, 0, file.Length);
279                         fs.Write (data, 0, data.Length);                // length (again)
280                         data = BitConverter.GetBytes (0x00020200);      // magic
281                         fs.Write (data, 0, data.Length);
282                         fs.Write (asn, 0, asn.Length);
283                         // fill up
284                         byte[] fillup = new byte [addsize];
285                         fs.Write (fillup, 0, fillup.Length);
286                         fs.Close ();
287
288                         return true;
289                 }
290
291                 // in case we just want to timestamp the file
292                 public bool Timestamp (string fileName) 
293                 {
294                         return true;
295                 }
296         }
297 }