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