2003-09-01 Sebastien Pouliot <spouliot@videotron.ca>
[mono.git] / mcs / class / Mono.Security / Mono.Security.Authenticode / AuthenticodeDeformatter.cs
1 //
2 // AuthenticodeSignature.cs: Authenticode signature validator and generator
3 //
4 // Author:
5 //      Sebastien Pouliot (spouliot@motus.com)
6 //
7 // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
8 //
9
10 using System;
11 using System.IO;
12 using System.Security.Cryptography;
13
14 using Mono.Security;
15 using Mono.Security.X509;
16
17 namespace Mono.Security.Authenticode {
18
19         // References:
20         // a.   http://www.cs.auckland.ac.nz/~pgut001/pubs/authenticode.txt
21
22         public class AuthenticodeDeformatter : AuthenticodeBase {
23
24                 private string filename;
25                 private byte[] hash;
26                 private X509CertificateCollection coll;
27                 private ASN1 signedHash;
28                 private DateTime timestamp;
29                 private X509Certificate signingCertificate;
30                 private int reason;
31
32                 private X509Chain signerChain;
33                 private X509Chain timestampChain;
34
35                 public AuthenticodeDeformatter () : base ()
36                 {
37                         reason = -1;
38                         signerChain = new X509Chain ();
39                         timestampChain = new X509Chain ();
40                 }
41
42                 public AuthenticodeDeformatter (string fileName) : this () 
43                 {
44                         CheckSignature (fileName);
45                 }
46
47                 public string FileName {
48                         get { return filename; }
49                         set { CheckSignature (value); }
50                 }
51
52                 public byte[] Hash {
53                         get { 
54                                 if (signedHash == null)
55                                         return null;
56                                 return signedHash.Value; 
57                         }
58                 }
59
60                 public int Reason {
61                         get { 
62                                 if (reason == -1)
63                                         IsTrusted ();
64                                 return reason; 
65                         }
66                 }
67
68                 public bool IsTrusted ()
69                 {
70                         if (rawData == null) {
71                                 reason = 1;
72                                 return false;
73                         }
74
75                         if (signingCertificate == null) {
76                                 reason = 7;
77                                 return false;
78                         }
79
80                         if (signerChain.Root == null) {
81                                 reason = 6;
82                                 return false;
83                         }
84
85                         if (timestamp != DateTime.MinValue) {
86                                 if (timestampChain.Root == null) {
87                                         reason = 6;
88                                         return false;
89                                 }
90
91                                 // check that file was timestamped when certificates were valid
92                                 if (!signingCertificate.WasCurrent (Timestamp)) {
93                                         reason = 4;
94                                         return false;
95                                 }
96                         }
97                         else if (!signingCertificate.IsCurrent) {
98                                 // signature only valid if the certificate is valid
99                                 reason = 8;
100                                 return false;
101                         }
102
103                         reason = 0;
104                         return true;
105                 }
106
107                 public byte[] Signature {
108                         get { return rawData; }
109                 }
110
111                 public DateTime Timestamp {
112                         get { return timestamp; }
113                 }
114
115                 public X509CertificateCollection Certificates {
116                         get { return coll; }
117                 }
118
119                 private bool CheckSignature (string fileName) 
120                 {
121                         filename = fileName;
122
123                         // by default we try with MD5
124                         string hashName = "MD5";
125                         // compare the signature's hash with the hash of the file
126                         hash = HashFile (filename, hashName);
127
128                         // is a signature present ?
129                         if (rawData == null)
130                                 return false;
131
132                         PKCS7.ContentInfo ci = new PKCS7.ContentInfo (rawData);
133                         if (ci.ContentType != PKCS7.signedData)
134                                 return false;
135
136                         PKCS7.SignedData sd = new PKCS7.SignedData (ci.Content);
137                         if (sd.ContentInfo.ContentType != spcIndirectDataContext)
138                                 return false;
139
140                         coll = sd.Certificates;
141
142                         ASN1 spc = sd.ContentInfo.Content;
143                         signedHash = spc [0][1][1];
144                         if (signedHash.Length == 20) {
145                                 // seems to be SHA-1, restart hashing
146                                 hashName = "SHA1";
147                                 hash = HashFile (filename, hashName);
148                         }
149
150                         if (!signedHash.CompareValue (hash))
151                                 return false;
152
153                         // messageDigest is a hash of spcIndirectDataContext (which includes the file hash)
154                         byte[] spcIDC = spc [0].Value;
155                         HashAlgorithm ha = HashAlgorithm.Create (hashName);
156                         byte[] messageDigest = ha.ComputeHash (spcIDC);
157
158                         return VerifySignature (sd, messageDigest, hashName);
159                 }
160
161                 private bool CompareIssuerSerial (string issuer, byte[] serial, X509Certificate x509) 
162                 {
163                         if (issuer != x509.IssuerName)
164                                 return false;
165                         if (serial.Length != x509.SerialNumber.Length)
166                                 return false;
167                         // MS shows the serial number inversed (so is Mono.Security.X509.X509Certificate)
168                         int n = serial.Length;
169                         for (int i=0; i < serial.Length; i++) {
170                                 if (serial [i] != x509.SerialNumber [--n])
171                                         return false;
172                         }
173                         // must be true
174                         return true;
175                 }
176
177                 //private bool VerifySignature (ASN1 cs, byte[] calculatedMessageDigest, string hashName) 
178                 private bool VerifySignature (PKCS7.SignedData sd, byte[] calculatedMessageDigest, string hashName) 
179                 {
180                         string contentType = null;
181                         ASN1 messageDigest = null;
182                         string spcStatementType = null;
183                         string spcSpOpusInfo = null;
184
185                         for (int i=0; i < sd.SignerInfo.AuthenticatedAttributes.Count; i++) {
186                                 ASN1 attr = (ASN1) sd.SignerInfo.AuthenticatedAttributes [i];
187                                 string oid = ASN1Convert.ToOID (attr[0]);
188                                 switch (oid) {
189                                         case "1.2.840.113549.1.9.3":
190                                                 // contentType
191                                                 contentType = ASN1Convert.ToOID (attr[1][0]);
192                                                 break;
193                                         case "1.2.840.113549.1.9.4":
194                                                 // messageDigest
195                                                 messageDigest = attr[1][0];
196                                                 break;
197                                         case "1.3.6.1.4.1.311.2.1.11":
198                                                 // spcStatementType (Microsoft code signing)
199                                                 // possible values
200                                                 // - individualCodeSigning (1 3 6 1 4 1 311 2 1 21)
201                                                 // - commercialCodeSigning (1 3 6 1 4 1 311 2 1 22)
202                                                 spcStatementType = ASN1Convert.ToOID (attr[1][0][0]);
203                                                 break;
204                                         case "1.3.6.1.4.1.311.2.1.12":
205                                                 // spcSpOpusInfo (Microsoft code signing)
206                                                 try {
207                                                         spcSpOpusInfo = System.Text.Encoding.UTF8.GetString (attr[1][0][1][0].Value);
208                                                 }
209                                                 catch {
210                                                         spcSpOpusInfo = null;
211                                                 }
212                                                 break;
213                                         default:
214                                                 break;
215                                 }
216                         }
217                         if (contentType != spcIndirectDataContext)
218                                 return false;
219
220                         // verify message digest
221                         if (messageDigest == null)
222                                 return false;
223                         if (!messageDigest.CompareValue (calculatedMessageDigest))
224                                 return false;
225
226                         // verify signature
227                         string hashOID = CryptoConfig.MapNameToOID (hashName);
228                         
229                         // change to SET OF (not [0]) as per PKCS #7 1.5
230                         ASN1 aa = new ASN1 (0x31);
231                         foreach (ASN1 a in sd.SignerInfo.AuthenticatedAttributes)
232                                 aa.Add (a);
233                         HashAlgorithm ha = HashAlgorithm.Create (hashName);
234                         byte[] p7hash = ha.ComputeHash (aa.GetBytes ());
235
236                         byte[] signature = sd.SignerInfo.Signature;
237                         // we need to find the specified certificate
238                         string issuer = sd.SignerInfo.IssuerName;
239                         byte[] serial = sd.SignerInfo.SerialNumber;
240                         foreach (X509Certificate x509 in coll) {
241                                 if (CompareIssuerSerial (issuer, serial, x509)) {
242                                         // don't verify is key size don't match
243                                         if (x509.PublicKey.Length > (signature.Length >> 3)) {
244                                                 RSACryptoServiceProvider rsa = (RSACryptoServiceProvider) x509.RSA;
245                                                 if (rsa.VerifyHash (p7hash, hashOID, signature)) {
246                                                         signerChain.LoadCertificates (coll);
247                                                         if (signerChain.GetChain (x509) != null)
248                                                                 signingCertificate = x509;
249                                                         else
250                                                                 return false;
251                                                 }
252                                         }
253                                 }
254                         }
255
256                         for (int i=0; i < sd.SignerInfo.UnauthenticatedAttributes.Count; i++) {
257                                 ASN1 attr = (ASN1) sd.SignerInfo.UnauthenticatedAttributes [i];
258                                 string oid = ASN1Convert.ToOID (attr [0]);
259                                 switch (oid) {
260                                         case PKCS7.countersignature:
261                                                 // SEQUENCE {
262                                                 //   OBJECT IDENTIFIER
263                                                 //     countersignature (1 2 840 113549 1 9 6)
264                                                 //   SET {
265                                                 PKCS7.SignerInfo cs = new PKCS7.SignerInfo (attr [1]);
266                                                 return VerifyCounterSignature (cs, signature, hashName);
267                                         default:
268                                                 // we don't support other unauthenticated attributes
269                                                 break;
270                                 }
271                         }
272
273                         return true;
274                 }
275
276                 //private bool VerifyCounterSignature (ASN1 cs, byte[] signature, string hashName) 
277                 private bool VerifyCounterSignature (PKCS7.SignerInfo cs, byte[] signature, string hashName) 
278                 {
279                         // SEQUENCE {
280                         //   INTEGER 1
281                         if (cs.Version != 1)
282                                 return false;
283                         //   SEQUENCE {
284                         //      SEQUENCE {
285
286                         string contentType = null;
287                         ASN1 messageDigest = null;
288                         for (int i=0; i < cs.AuthenticatedAttributes.Count; i++) {
289                                 // SEQUENCE {
290                                 //   OBJECT IDENTIFIER
291                                 ASN1 attr = (ASN1) cs.AuthenticatedAttributes [i];
292                                 string oid = ASN1Convert.ToOID (attr[0]);
293                                 switch (oid) {
294                                         case "1.2.840.113549.1.9.3":
295                                                 // contentType
296                                                 contentType = ASN1Convert.ToOID (attr[1][0]);
297                                                 break;
298                                         case "1.2.840.113549.1.9.4":
299                                                 // messageDigest
300                                                 messageDigest = attr[1][0];
301                                                 break;
302                                         case "1.2.840.113549.1.9.5":
303                                                 // SEQUENCE {
304                                                 //   OBJECT IDENTIFIER
305                                                 //     signingTime (1 2 840 113549 1 9 5)
306                                                 //   SET {
307                                                 //     UTCTime '030124013651Z'
308                                                 //   }
309                                                 // }
310                                                 timestamp = ASN1Convert.ToDateTime (attr[1][0]);
311                                                 break;
312                                         default:
313                                                 break;
314                                 }
315                         }
316
317                         if (contentType != PKCS7.data) 
318                                 return false;
319
320                         // verify message digest
321                         if (messageDigest == null)
322                                 return false;
323                         // TODO: must be read from the ASN.1 structure
324                         switch (messageDigest.Length) {
325                                 case 16:
326                                         hashName = "MD5";
327                                         break;
328                                 case 20:
329                                         hashName = "SHA1";
330                                         break;
331                         }
332                         HashAlgorithm ha = HashAlgorithm.Create (hashName);
333                         if (!messageDigest.CompareValue (ha.ComputeHash (signature)))
334                                 return false;
335
336                         // verify signature
337                         byte[] counterSignature = cs.Signature;
338                         string hashOID = CryptoConfig.MapNameToOID (hashName);
339
340                         // change to SET OF (not [0]) as per PKCS #7 1.5
341                         ASN1 aa = new ASN1 (0x31);
342                         foreach (ASN1 a in cs.AuthenticatedAttributes)
343                                 aa.Add (a);
344                         byte[] p7hash = ha.ComputeHash (aa.GetBytes ());
345
346                         // we need to try all certificates
347                         string issuer = cs.IssuerName;
348                         byte[] serial = cs.SerialNumber;
349                         foreach (X509Certificate x509 in coll) {
350                                 if (CompareIssuerSerial (issuer, serial, x509)) {
351                                         // don't verify is key size don't match
352                                         if (x509.PublicKey.Length > (counterSignature.Length >> 3)) {
353                                                 RSACryptoServiceProvider rsa = (RSACryptoServiceProvider) x509.RSA;
354                                                 if (rsa.VerifyHash (p7hash, hashOID, counterSignature)) {
355                                                         timestampChain.LoadCertificates (coll);
356                                                         return (timestampChain.GetChain (x509) != null);
357                                                 }
358                                         }
359                                 }
360                         }
361                         // no certificate can verify this signature!
362                         return false;
363                 }
364         }
365 }