[xbuild] Actually delete common files (CopyLocal) during Clean.
[mono.git] / mcs / class / corlib / Mono.Security.Authenticode / AuthenticodeDeformatter.cs
1 //
2 // AuthenticodeDeformatter.cs: Authenticode signature validator
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 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.Runtime.InteropServices;
33 using System.Security;
34 using System.Security.Cryptography;
35
36 using Mono.Security.Cryptography;
37 using Mono.Security.X509;
38
39 namespace Mono.Security.Authenticode {
40
41         // References:
42         // a.   http://www.cs.auckland.ac.nz/~pgut001/pubs/authenticode.txt
43
44 #if INSIDE_CORLIB
45         internal
46 #else
47         public 
48 #endif
49         class AuthenticodeDeformatter : AuthenticodeBase {
50
51                 private string filename;
52                 private byte[] hash;
53                 private X509CertificateCollection coll;
54                 private ASN1 signedHash;
55                 private DateTime timestamp;
56                 private X509Certificate signingCertificate;
57                 private int reason;
58                 private bool trustedRoot;
59                 private bool trustedTimestampRoot;
60                 private byte[] entry;
61
62                 private X509Chain signerChain;
63                 private X509Chain timestampChain;
64
65                 public AuthenticodeDeformatter () : base ()
66                 {
67                         reason = -1;
68                         signerChain = new X509Chain ();
69                         timestampChain = new X509Chain ();
70                 }
71
72                 public AuthenticodeDeformatter (string fileName) : this () 
73                 {
74                         FileName = fileName;
75                 }
76
77                 public string FileName {
78                         get { return filename; }
79                         set { 
80                                 Reset ();
81                                 try {
82                                         CheckSignature (value); 
83                                 }
84                                 catch (SecurityException) {
85                                         throw;
86                                 }
87                                 catch (Exception) {
88                                         reason = 1;
89                                 }
90                         }
91                 }
92
93                 public byte[] Hash {
94                         get { 
95                                 if (signedHash == null)
96                                         return null;
97                                 return (byte[]) signedHash.Value.Clone ();
98                         }
99                 }
100
101                 public int Reason {
102                         get { 
103                                 if (reason == -1)
104                                         IsTrusted ();
105                                 return reason; 
106                         }
107                 }
108
109                 public bool IsTrusted ()
110                 {
111                         if (entry == null) {
112                                 reason = 1;
113                                 return false;
114                         }
115
116                         if (signingCertificate == null) {
117                                 reason = 7;
118                                 return false;
119                         }
120
121                         if ((signerChain.Root == null) || !trustedRoot) {
122                                 reason = 6;
123                                 return false;
124                         }
125
126                         if (timestamp != DateTime.MinValue) {
127                                 if ((timestampChain.Root == null) || !trustedTimestampRoot) {
128                                         reason = 6;
129                                         return false;
130                                 }
131
132                                 // check that file was timestamped when certificates were valid
133                                 if (!signingCertificate.WasCurrent (Timestamp)) {
134                                         reason = 4;
135                                         return false;
136                                 }
137                         }
138                         else if (!signingCertificate.IsCurrent) {
139                                 // signature only valid if the certificate is valid
140                                 reason = 8;
141                                 return false;
142                         }
143
144                         if (reason == -1)
145                                 reason = 0;
146                         return true;
147                 }
148
149                 public byte[] Signature {
150                         get {
151                                 if (entry == null)
152                                         return null;
153                                 return (byte[]) entry.Clone (); 
154                         }
155                 }
156
157                 public DateTime Timestamp {
158                         get { return timestamp; }
159                 }
160
161                 public X509CertificateCollection Certificates {
162                         get { return coll; }
163                 }
164
165                 public X509Certificate SigningCertificate {
166                         get { return signingCertificate; }
167                 }
168
169                 private bool CheckSignature (string fileName) 
170                 {
171                         filename = fileName;
172                         Open (filename);
173                         entry = GetSecurityEntry ();
174                         if (entry == null) {
175                                 // no signature is present
176                                 reason = 1;
177                                 Close ();
178                                 return false;
179                         }
180
181                         PKCS7.ContentInfo ci = new PKCS7.ContentInfo (entry);
182                         if (ci.ContentType != PKCS7.Oid.signedData) {
183                                 Close ();
184                                 return false;
185                         }
186
187                         PKCS7.SignedData sd = new PKCS7.SignedData (ci.Content);
188                         if (sd.ContentInfo.ContentType != spcIndirectDataContext) {
189                                 Close ();
190                                 return false;
191                         }
192
193                         coll = sd.Certificates;
194
195                         ASN1 spc = sd.ContentInfo.Content;
196                         signedHash = spc [0][1][1];
197
198                         HashAlgorithm ha = null; 
199                         switch (signedHash.Length) {
200                                 case 16:
201                                         ha = HashAlgorithm.Create ("MD5"); 
202                                         hash = GetHash (ha);
203                                         break;
204                                 case 20:
205                                         ha = HashAlgorithm.Create ("SHA1");
206                                         hash = GetHash (ha);
207                                         break;
208                                 default:
209                                         reason = 5;
210                                         Close ();
211                                         return false;
212                         }
213                         Close ();
214
215                         if (!signedHash.CompareValue (hash)) {
216                                 reason = 2;
217                         }
218
219                         // messageDigest is a hash of spcIndirectDataContext (which includes the file hash)
220                         byte[] spcIDC = spc [0].Value;
221                         ha.Initialize (); // re-using hash instance
222                         byte[] messageDigest = ha.ComputeHash (spcIDC);
223
224                         bool sign = VerifySignature (sd, messageDigest, ha);
225                         return (sign && (reason == 0));
226                 }
227
228                 private bool CompareIssuerSerial (string issuer, byte[] serial, X509Certificate x509) 
229                 {
230                         if (issuer != x509.IssuerName)
231                                 return false;
232                         if (serial.Length != x509.SerialNumber.Length)
233                                 return false;
234                         // MS shows the serial number inversed (so is Mono.Security.X509.X509Certificate)
235                         int n = serial.Length;
236                         for (int i=0; i < serial.Length; i++) {
237                                 if (serial [i] != x509.SerialNumber [--n])
238                                         return false;
239                         }
240                         // must be true
241                         return true;
242                 }
243
244                 //private bool VerifySignature (ASN1 cs, byte[] calculatedMessageDigest, string hashName) 
245                 private bool VerifySignature (PKCS7.SignedData sd, byte[] calculatedMessageDigest, HashAlgorithm ha) 
246                 {
247                         string contentType = null;
248                         ASN1 messageDigest = null;
249 //                      string spcStatementType = null;
250 //                      string spcSpOpusInfo = null;
251
252                         for (int i=0; i < sd.SignerInfo.AuthenticatedAttributes.Count; i++) {
253                                 ASN1 attr = (ASN1) sd.SignerInfo.AuthenticatedAttributes [i];
254                                 string oid = ASN1Convert.ToOid (attr[0]);
255                                 switch (oid) {
256                                         case "1.2.840.113549.1.9.3":
257                                                 // contentType
258                                                 contentType = ASN1Convert.ToOid (attr[1][0]);
259                                                 break;
260                                         case "1.2.840.113549.1.9.4":
261                                                 // messageDigest
262                                                 messageDigest = attr[1][0];
263                                                 break;
264                                         case "1.3.6.1.4.1.311.2.1.11":
265                                                 // spcStatementType (Microsoft code signing)
266                                                 // possible values
267                                                 // - individualCodeSigning (1 3 6 1 4 1 311 2 1 21)
268                                                 // - commercialCodeSigning (1 3 6 1 4 1 311 2 1 22)
269 //                                              spcStatementType = ASN1Convert.ToOid (attr[1][0][0]);
270                                                 break;
271                                         case "1.3.6.1.4.1.311.2.1.12":
272                                                 // spcSpOpusInfo (Microsoft code signing)
273 /*                                              try {
274                                                         spcSpOpusInfo = System.Text.Encoding.UTF8.GetString (attr[1][0][0][0].Value);
275                                                 }
276                                                 catch (NullReferenceException) {
277                                                         spcSpOpusInfo = null;
278                                                 }*/
279                                                 break;
280                                         default:
281                                                 break;
282                                 }
283                         }
284                         if (contentType != spcIndirectDataContext)
285                                 return false;
286
287                         // verify message digest
288                         if (messageDigest == null)
289                                 return false;
290                         if (!messageDigest.CompareValue (calculatedMessageDigest))
291                                 return false;
292
293                         // verify signature
294                         string hashOID = CryptoConfig.MapNameToOID (ha.ToString ());
295                         
296                         // change to SET OF (not [0]) as per PKCS #7 1.5
297                         ASN1 aa = new ASN1 (0x31);
298                         foreach (ASN1 a in sd.SignerInfo.AuthenticatedAttributes)
299                                 aa.Add (a);
300                         ha.Initialize ();
301                         byte[] p7hash = ha.ComputeHash (aa.GetBytes ());
302
303                         byte[] signature = sd.SignerInfo.Signature;
304                         // we need to find the specified certificate
305                         string issuer = sd.SignerInfo.IssuerName;
306                         byte[] serial = sd.SignerInfo.SerialNumber;
307                         foreach (X509Certificate x509 in coll) {
308                                 if (CompareIssuerSerial (issuer, serial, x509)) {
309                                         // don't verify is key size don't match
310                                         if (x509.PublicKey.Length > (signature.Length >> 3)) {
311                                                 // return the signing certificate even if the signature isn't correct
312                                                 // (required behaviour for 2.0 support)
313                                                 signingCertificate = x509;
314                                                 RSACryptoServiceProvider rsa = (RSACryptoServiceProvider) x509.RSA;
315                                                 if (rsa.VerifyHash (p7hash, hashOID, signature)) {
316                                                         signerChain.LoadCertificates (coll);
317                                                         trustedRoot = signerChain.Build (x509);
318                                                         break; 
319                                                 }
320                                         }
321                                 }
322                         }
323
324                         // timestamp signature is optional
325                         if (sd.SignerInfo.UnauthenticatedAttributes.Count == 0) {
326                                 trustedTimestampRoot = true;
327                         }  else {
328                                 for (int i = 0; i < sd.SignerInfo.UnauthenticatedAttributes.Count; i++) {
329                                         ASN1 attr = (ASN1) sd.SignerInfo.UnauthenticatedAttributes[i];
330                                         string oid = ASN1Convert.ToOid (attr[0]);
331                                         switch (oid) {
332                                         case PKCS7.Oid.countersignature:
333                                                 // SEQUENCE {
334                                                 //   OBJECT IDENTIFIER
335                                                 //     countersignature (1 2 840 113549 1 9 6)
336                                                 //   SET {
337                                                 PKCS7.SignerInfo cs = new PKCS7.SignerInfo (attr[1]);
338                                                 trustedTimestampRoot = VerifyCounterSignature (cs, signature);
339                                                 break;
340                                         default:
341                                                 // we don't support other unauthenticated attributes
342                                                 break;
343                                         }
344                                 }
345                         }
346
347                         return (trustedRoot && trustedTimestampRoot);
348                 }
349
350                 private bool VerifyCounterSignature (PKCS7.SignerInfo cs, byte[] signature) 
351                 {
352                         // SEQUENCE {
353                         //   INTEGER 1
354                         if (cs.Version != 1)
355                                 return false;
356                         //   SEQUENCE {
357                         //      SEQUENCE {
358
359                         string contentType = null;
360                         ASN1 messageDigest = null;
361                         for (int i=0; i < cs.AuthenticatedAttributes.Count; i++) {
362                                 // SEQUENCE {
363                                 //   OBJECT IDENTIFIER
364                                 ASN1 attr = (ASN1) cs.AuthenticatedAttributes [i];
365                                 string oid = ASN1Convert.ToOid (attr[0]);
366                                 switch (oid) {
367                                         case "1.2.840.113549.1.9.3":
368                                                 // contentType
369                                                 contentType = ASN1Convert.ToOid (attr[1][0]);
370                                                 break;
371                                         case "1.2.840.113549.1.9.4":
372                                                 // messageDigest
373                                                 messageDigest = attr[1][0];
374                                                 break;
375                                         case "1.2.840.113549.1.9.5":
376                                                 // SEQUENCE {
377                                                 //   OBJECT IDENTIFIER
378                                                 //     signingTime (1 2 840 113549 1 9 5)
379                                                 //   SET {
380                                                 //     UTCTime '030124013651Z'
381                                                 //   }
382                                                 // }
383                                                 timestamp = ASN1Convert.ToDateTime (attr[1][0]);
384                                                 break;
385                                         default:
386                                                 break;
387                                 }
388                         }
389
390                         if (contentType != PKCS7.Oid.data) 
391                                 return false;
392
393                         // verify message digest
394                         if (messageDigest == null)
395                                 return false;
396                         // TODO: must be read from the ASN.1 structure
397                         string hashName = null;
398                         switch (messageDigest.Length) {
399                                 case 16:
400                                         hashName = "MD5";
401                                         break;
402                                 case 20:
403                                         hashName = "SHA1";
404                                         break;
405                         }
406                         HashAlgorithm ha = HashAlgorithm.Create (hashName);
407                         if (!messageDigest.CompareValue (ha.ComputeHash (signature)))
408                                 return false;
409
410                         // verify signature
411                         byte[] counterSignature = cs.Signature;
412
413                         // change to SET OF (not [0]) as per PKCS #7 1.5
414                         ASN1 aa = new ASN1 (0x31);
415                         foreach (ASN1 a in cs.AuthenticatedAttributes)
416                                 aa.Add (a);
417                         byte[] p7hash = ha.ComputeHash (aa.GetBytes ());
418
419                         // we need to try all certificates
420                         string issuer = cs.IssuerName;
421                         byte[] serial = cs.SerialNumber;
422                         foreach (X509Certificate x509 in coll) {
423                                 if (CompareIssuerSerial (issuer, serial, x509)) {
424                                         if (x509.PublicKey.Length > counterSignature.Length) {
425                                                 RSACryptoServiceProvider rsa = (RSACryptoServiceProvider) x509.RSA;
426                                                 // we need to HACK around bad (PKCS#1 1.5) signatures made by Verisign Timestamp Service
427                                                 // and this means copying stuff into our own RSAManaged to get the required flexibility
428                                                 RSAManaged rsam = new RSAManaged ();
429                                                 rsam.ImportParameters (rsa.ExportParameters (false));
430                                                 if (PKCS1.Verify_v15 (rsam, ha, p7hash, counterSignature, true)) {
431                                                         timestampChain.LoadCertificates (coll);
432                                                         return (timestampChain.Build (x509));
433                                                 }
434                                         }
435                                 }
436                         }
437                         // no certificate can verify this signature!
438                         return false;
439                 }
440
441                 private void Reset ()
442                 {
443                         filename = null;
444                         entry = null;
445                         hash = null;
446                         signedHash = null;
447                         signingCertificate = null;
448                         reason = -1;
449                         trustedRoot = false;
450                         trustedTimestampRoot = false;
451                         signerChain.Reset ();
452                         timestampChain.Reset ();
453                         timestamp = DateTime.MinValue;
454                 }
455         }
456 }