2004-07-07 Sebastien Pouliot <sebastien@ximian.com>
[mono.git] / mcs / class / corlib / Mono.Security.Authenticode / AuthenticodeDeformatter.cs
1 //
2 // AuthenticodeDeformatter.cs: Authenticode signature validator
3 //
4 // Author:
5 //      Sebastien Pouliot (spouliot@motus.com)
6 //
7 // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
8 //
9
10 //
11 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 // 
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 // 
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32
33 using System;
34 using System.IO;
35 using System.Runtime.InteropServices;
36 using System.Security.Cryptography;
37
38 using Mono.Security;
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
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                         if (!CheckSignature (fileName)) {
74                                 // invalid or no signature
75                                 if (signedHash != null)
76                                         throw new COMException ("Invalid signature");
77                                 // no exception is thrown when there's no signature in the PE file
78                         }
79                 }
80
81                 public string FileName {
82                         get { return filename; }
83                         set { CheckSignature (value); }
84                 }
85
86                 public byte[] Hash {
87                         get { 
88                                 if (signedHash == null)
89                                         return null;
90                                 return (byte[]) signedHash.Value.Clone ();
91                         }
92                 }
93
94                 public int Reason {
95                         get { 
96                                 if (reason == -1)
97                                         IsTrusted ();
98                                 return reason; 
99                         }
100                 }
101
102                 public bool IsTrusted ()
103                 {
104                         if (rawData == null) {
105                                 reason = 1;
106                                 return false;
107                         }
108
109                         if (signingCertificate == null) {
110                                 reason = 7;
111                                 return false;
112                         }
113
114                         if (signerChain.Root == null) {
115                                 reason = 6;
116                                 return false;
117                         }
118
119                         if (timestamp != DateTime.MinValue) {
120                                 if (timestampChain.Root == null) {
121                                         reason = 6;
122                                         return false;
123                                 }
124
125                                 // check that file was timestamped when certificates were valid
126                                 if (!signingCertificate.WasCurrent (Timestamp)) {
127                                         reason = 4;
128                                         return false;
129                                 }
130                         }
131                         else if (!signingCertificate.IsCurrent) {
132                                 // signature only valid if the certificate is valid
133                                 reason = 8;
134                                 return false;
135                         }
136
137                         reason = 0;
138                         return true;
139                 }
140
141                 public byte[] Signature {
142                         get { return (byte[]) rawData.Clone (); }
143                 }
144
145                 public DateTime Timestamp {
146                         get { return timestamp; }
147                 }
148
149                 public X509CertificateCollection Certificates {
150                         get { return coll; }
151                 }
152
153                 public X509Certificate SigningCertificate {
154                         get { return signingCertificate; }
155                 }
156
157                 private bool CheckSignature (string fileName) 
158                 {
159                         filename = fileName;
160
161                         // by default we try with MD5
162                         string hashName = "MD5";
163                         // compare the signature's hash with the hash of the file
164                         hash = HashFile (filename, hashName);
165
166                         // is a signature present ?
167                         if (rawData == null)
168                                 return false;
169
170                         PKCS7.ContentInfo ci = new PKCS7.ContentInfo (rawData);
171                         if (ci.ContentType != PKCS7.Oid.signedData)
172                                 return false;
173
174                         PKCS7.SignedData sd = new PKCS7.SignedData (ci.Content);
175                         if (sd.ContentInfo.ContentType != spcIndirectDataContext)
176                                 return false;
177
178                         coll = sd.Certificates;
179
180                         ASN1 spc = sd.ContentInfo.Content;
181                         signedHash = spc [0][1][1];
182                         if (signedHash.Length == 20) {
183                                 // seems to be SHA-1, restart hashing
184                                 hashName = "SHA1";
185                                 hash = HashFile (filename, hashName);
186                         }
187
188                         if (!signedHash.CompareValue (hash))
189                                 return false;
190
191                         // messageDigest is a hash of spcIndirectDataContext (which includes the file hash)
192                         byte[] spcIDC = spc [0].Value;
193                         HashAlgorithm ha = HashAlgorithm.Create (hashName);
194                         byte[] messageDigest = ha.ComputeHash (spcIDC);
195
196                         return VerifySignature (sd, messageDigest, hashName);
197                 }
198
199                 private bool CompareIssuerSerial (string issuer, byte[] serial, X509Certificate x509) 
200                 {
201                         if (issuer != x509.IssuerName)
202                                 return false;
203                         if (serial.Length != x509.SerialNumber.Length)
204                                 return false;
205                         // MS shows the serial number inversed (so is Mono.Security.X509.X509Certificate)
206                         int n = serial.Length;
207                         for (int i=0; i < serial.Length; i++) {
208                                 if (serial [i] != x509.SerialNumber [--n])
209                                         return false;
210                         }
211                         // must be true
212                         return true;
213                 }
214
215                 //private bool VerifySignature (ASN1 cs, byte[] calculatedMessageDigest, string hashName) 
216                 private bool VerifySignature (PKCS7.SignedData sd, byte[] calculatedMessageDigest, string hashName) 
217                 {
218                         string contentType = null;
219                         ASN1 messageDigest = null;
220                         string spcStatementType = null;
221                         string spcSpOpusInfo = null;
222
223                         for (int i=0; i < sd.SignerInfo.AuthenticatedAttributes.Count; i++) {
224                                 ASN1 attr = (ASN1) sd.SignerInfo.AuthenticatedAttributes [i];
225                                 string oid = ASN1Convert.ToOid (attr[0]);
226                                 switch (oid) {
227                                         case "1.2.840.113549.1.9.3":
228                                                 // contentType
229                                                 contentType = ASN1Convert.ToOid (attr[1][0]);
230                                                 break;
231                                         case "1.2.840.113549.1.9.4":
232                                                 // messageDigest
233                                                 messageDigest = attr[1][0];
234                                                 break;
235                                         case "1.3.6.1.4.1.311.2.1.11":
236                                                 // spcStatementType (Microsoft code signing)
237                                                 // possible values
238                                                 // - individualCodeSigning (1 3 6 1 4 1 311 2 1 21)
239                                                 // - commercialCodeSigning (1 3 6 1 4 1 311 2 1 22)
240                                                 spcStatementType = ASN1Convert.ToOid (attr[1][0][0]);
241                                                 break;
242                                         case "1.3.6.1.4.1.311.2.1.12":
243                                                 // spcSpOpusInfo (Microsoft code signing)
244                                                 try {
245                                                         spcSpOpusInfo = System.Text.Encoding.UTF8.GetString (attr[1][0][1][0].Value);
246                                                 }
247                                                 catch (NullReferenceException) {
248                                                         spcSpOpusInfo = null;
249                                                 }
250                                                 break;
251                                         default:
252                                                 break;
253                                 }
254                         }
255                         if (contentType != spcIndirectDataContext)
256                                 return false;
257
258                         // verify message digest
259                         if (messageDigest == null)
260                                 return false;
261                         if (!messageDigest.CompareValue (calculatedMessageDigest))
262                                 return false;
263
264                         // verify signature
265                         string hashOID = CryptoConfig.MapNameToOID (hashName);
266                         
267                         // change to SET OF (not [0]) as per PKCS #7 1.5
268                         ASN1 aa = new ASN1 (0x31);
269                         foreach (ASN1 a in sd.SignerInfo.AuthenticatedAttributes)
270                                 aa.Add (a);
271                         HashAlgorithm ha = HashAlgorithm.Create (hashName);
272                         byte[] p7hash = ha.ComputeHash (aa.GetBytes ());
273
274                         byte[] signature = sd.SignerInfo.Signature;
275                         // we need to find the specified certificate
276                         string issuer = sd.SignerInfo.IssuerName;
277                         byte[] serial = sd.SignerInfo.SerialNumber;
278                         foreach (X509Certificate x509 in coll) {
279                                 if (CompareIssuerSerial (issuer, serial, x509)) {
280                                         // don't verify is key size don't match
281                                         if (x509.PublicKey.Length > (signature.Length >> 3)) {
282                                                 RSACryptoServiceProvider rsa = (RSACryptoServiceProvider) x509.RSA;
283                                                 if (rsa.VerifyHash (p7hash, hashOID, signature)) {
284                                                         signerChain.LoadCertificates (coll);
285                                                         if (signerChain.Build (x509))
286                                                                 signingCertificate = x509;
287                                                         else
288                                                                 return false;
289                                                 }
290                                         }
291                                 }
292                         }
293
294                         for (int i=0; i < sd.SignerInfo.UnauthenticatedAttributes.Count; i++) {
295                                 ASN1 attr = (ASN1) sd.SignerInfo.UnauthenticatedAttributes [i];
296                                 string oid = ASN1Convert.ToOid (attr [0]);
297                                 switch (oid) {
298                                         case PKCS7.Oid.countersignature:
299                                                 // SEQUENCE {
300                                                 //   OBJECT IDENTIFIER
301                                                 //     countersignature (1 2 840 113549 1 9 6)
302                                                 //   SET {
303                                                 PKCS7.SignerInfo cs = new PKCS7.SignerInfo (attr [1]);
304                                                 return VerifyCounterSignature (cs, signature, hashName);
305                                         default:
306                                                 // we don't support other unauthenticated attributes
307                                                 break;
308                                 }
309                         }
310
311                         return true;
312                 }
313
314                 private bool VerifyCounterSignature (PKCS7.SignerInfo cs, byte[] signature, string hashName) 
315                 {
316                         // SEQUENCE {
317                         //   INTEGER 1
318                         if (cs.Version != 1)
319                                 return false;
320                         //   SEQUENCE {
321                         //      SEQUENCE {
322
323                         string contentType = null;
324                         ASN1 messageDigest = null;
325                         for (int i=0; i < cs.AuthenticatedAttributes.Count; i++) {
326                                 // SEQUENCE {
327                                 //   OBJECT IDENTIFIER
328                                 ASN1 attr = (ASN1) cs.AuthenticatedAttributes [i];
329                                 string oid = ASN1Convert.ToOid (attr[0]);
330                                 switch (oid) {
331                                         case "1.2.840.113549.1.9.3":
332                                                 // contentType
333                                                 contentType = ASN1Convert.ToOid (attr[1][0]);
334                                                 break;
335                                         case "1.2.840.113549.1.9.4":
336                                                 // messageDigest
337                                                 messageDigest = attr[1][0];
338                                                 break;
339                                         case "1.2.840.113549.1.9.5":
340                                                 // SEQUENCE {
341                                                 //   OBJECT IDENTIFIER
342                                                 //     signingTime (1 2 840 113549 1 9 5)
343                                                 //   SET {
344                                                 //     UTCTime '030124013651Z'
345                                                 //   }
346                                                 // }
347                                                 timestamp = ASN1Convert.ToDateTime (attr[1][0]);
348                                                 break;
349                                         default:
350                                                 break;
351                                 }
352                         }
353
354                         if (contentType != PKCS7.Oid.data) 
355                                 return false;
356
357                         // verify message digest
358                         if (messageDigest == null)
359                                 return false;
360                         // TODO: must be read from the ASN.1 structure
361                         switch (messageDigest.Length) {
362                                 case 16:
363                                         hashName = "MD5";
364                                         break;
365                                 case 20:
366                                         hashName = "SHA1";
367                                         break;
368                         }
369                         HashAlgorithm ha = HashAlgorithm.Create (hashName);
370                         if (!messageDigest.CompareValue (ha.ComputeHash (signature)))
371                                 return false;
372
373                         // verify signature
374                         byte[] counterSignature = cs.Signature;
375                         string hashOID = CryptoConfig.MapNameToOID (hashName);
376
377                         // change to SET OF (not [0]) as per PKCS #7 1.5
378                         ASN1 aa = new ASN1 (0x31);
379                         foreach (ASN1 a in cs.AuthenticatedAttributes)
380                                 aa.Add (a);
381                         byte[] p7hash = ha.ComputeHash (aa.GetBytes ());
382
383                         // we need to try all certificates
384                         string issuer = cs.IssuerName;
385                         byte[] serial = cs.SerialNumber;
386                         foreach (X509Certificate x509 in coll) {
387                                 if (CompareIssuerSerial (issuer, serial, x509)) {
388                                         // don't verify if key size don't match
389                                         if (x509.PublicKey.Length > (counterSignature.Length >> 3)) {
390                                                 RSACryptoServiceProvider rsa = (RSACryptoServiceProvider) x509.RSA;
391                                                 if (rsa.VerifyHash (p7hash, hashOID, counterSignature)) {
392                                                         timestampChain.LoadCertificates (coll);
393                                                         return (timestampChain.Build (x509));
394                                                 }
395                                         }
396                                 }
397                         }
398                         // no certificate can verify this signature!
399                         return false;
400                 }
401         }
402 }