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