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