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