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