bring Mono Security to monotouch
[mcs.git] / class / corlib / Mono.Security.Authenticode / AuthenticodeDeformatter.cs
blob389b01ddd7fc993eb492799435b9431917147999
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-2006 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.
30 #if !NET_2_1 || MONOTOUCH
32 using System;
33 using System.IO;
34 using System.Runtime.InteropServices;
35 using System.Security;
36 using System.Security.Cryptography;
38 using Mono.Security.Cryptography;
39 using Mono.Security.X509;
41 namespace Mono.Security.Authenticode {
43 // References:
44 // a. http://www.cs.auckland.ac.nz/~pgut001/pubs/authenticode.txt
46 #if INSIDE_CORLIB
47 internal
48 #else
49 public
50 #endif
51 class AuthenticodeDeformatter : AuthenticodeBase {
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 private bool trustedRoot;
61 private bool trustedTimestampRoot;
62 private byte[] entry;
64 private X509Chain signerChain;
65 private X509Chain timestampChain;
67 public AuthenticodeDeformatter () : base ()
69 reason = -1;
70 signerChain = new X509Chain ();
71 timestampChain = new X509Chain ();
74 public AuthenticodeDeformatter (string fileName) : this ()
76 FileName = fileName;
79 public string FileName {
80 get { return filename; }
81 set {
82 Reset ();
83 try {
84 CheckSignature (value);
86 catch (SecurityException) {
87 throw;
89 catch (Exception) {
90 reason = 1;
95 public byte[] Hash {
96 get {
97 if (signedHash == null)
98 return null;
99 return (byte[]) signedHash.Value.Clone ();
103 public int Reason {
104 get {
105 if (reason == -1)
106 IsTrusted ();
107 return reason;
111 public bool IsTrusted ()
113 if (entry == null) {
114 reason = 1;
115 return false;
118 if (signingCertificate == null) {
119 reason = 7;
120 return false;
123 if ((signerChain.Root == null) || !trustedRoot) {
124 reason = 6;
125 return false;
128 if (timestamp != DateTime.MinValue) {
129 if ((timestampChain.Root == null) || !trustedTimestampRoot) {
130 reason = 6;
131 return false;
134 // check that file was timestamped when certificates were valid
135 if (!signingCertificate.WasCurrent (Timestamp)) {
136 reason = 4;
137 return false;
140 else if (!signingCertificate.IsCurrent) {
141 // signature only valid if the certificate is valid
142 reason = 8;
143 return false;
146 if (reason == -1)
147 reason = 0;
148 return true;
151 public byte[] Signature {
152 get {
153 if (entry == null)
154 return null;
155 return (byte[]) entry.Clone ();
159 public DateTime Timestamp {
160 get { return timestamp; }
163 public X509CertificateCollection Certificates {
164 get { return coll; }
167 public X509Certificate SigningCertificate {
168 get { return signingCertificate; }
171 private bool CheckSignature (string fileName)
173 filename = fileName;
174 Open (filename);
175 entry = GetSecurityEntry ();
176 if (entry == null) {
177 // no signature is present
178 reason = 1;
179 Close ();
180 return false;
183 PKCS7.ContentInfo ci = new PKCS7.ContentInfo (entry);
184 if (ci.ContentType != PKCS7.Oid.signedData) {
185 Close ();
186 return false;
189 PKCS7.SignedData sd = new PKCS7.SignedData (ci.Content);
190 if (sd.ContentInfo.ContentType != spcIndirectDataContext) {
191 Close ();
192 return false;
195 coll = sd.Certificates;
197 ASN1 spc = sd.ContentInfo.Content;
198 signedHash = spc [0][1][1];
200 HashAlgorithm ha = null;
201 switch (signedHash.Length) {
202 case 16:
203 ha = HashAlgorithm.Create ("MD5");
204 hash = GetHash (ha);
205 break;
206 case 20:
207 ha = HashAlgorithm.Create ("SHA1");
208 hash = GetHash (ha);
209 break;
210 default:
211 reason = 5;
212 Close ();
213 return false;
215 Close ();
217 if (!signedHash.CompareValue (hash)) {
218 reason = 2;
221 // messageDigest is a hash of spcIndirectDataContext (which includes the file hash)
222 byte[] spcIDC = spc [0].Value;
223 ha.Initialize (); // re-using hash instance
224 byte[] messageDigest = ha.ComputeHash (spcIDC);
226 bool sign = VerifySignature (sd, messageDigest, ha);
227 return (sign && (reason == 0));
230 private bool CompareIssuerSerial (string issuer, byte[] serial, X509Certificate x509)
232 if (issuer != x509.IssuerName)
233 return false;
234 if (serial.Length != x509.SerialNumber.Length)
235 return false;
236 // MS shows the serial number inversed (so is Mono.Security.X509.X509Certificate)
237 int n = serial.Length;
238 for (int i=0; i < serial.Length; i++) {
239 if (serial [i] != x509.SerialNumber [--n])
240 return false;
242 // must be true
243 return true;
246 //private bool VerifySignature (ASN1 cs, byte[] calculatedMessageDigest, string hashName)
247 private bool VerifySignature (PKCS7.SignedData sd, byte[] calculatedMessageDigest, HashAlgorithm ha)
249 string contentType = null;
250 ASN1 messageDigest = null;
251 // string spcStatementType = null;
252 // string spcSpOpusInfo = null;
254 for (int i=0; i < sd.SignerInfo.AuthenticatedAttributes.Count; i++) {
255 ASN1 attr = (ASN1) sd.SignerInfo.AuthenticatedAttributes [i];
256 string oid = ASN1Convert.ToOid (attr[0]);
257 switch (oid) {
258 case "1.2.840.113549.1.9.3":
259 // contentType
260 contentType = ASN1Convert.ToOid (attr[1][0]);
261 break;
262 case "1.2.840.113549.1.9.4":
263 // messageDigest
264 messageDigest = attr[1][0];
265 break;
266 case "1.3.6.1.4.1.311.2.1.11":
267 // spcStatementType (Microsoft code signing)
268 // possible values
269 // - individualCodeSigning (1 3 6 1 4 1 311 2 1 21)
270 // - commercialCodeSigning (1 3 6 1 4 1 311 2 1 22)
271 // spcStatementType = ASN1Convert.ToOid (attr[1][0][0]);
272 break;
273 case "1.3.6.1.4.1.311.2.1.12":
274 // spcSpOpusInfo (Microsoft code signing)
275 /* try {
276 spcSpOpusInfo = System.Text.Encoding.UTF8.GetString (attr[1][0][0][0].Value);
278 catch (NullReferenceException) {
279 spcSpOpusInfo = null;
281 break;
282 default:
283 break;
286 if (contentType != spcIndirectDataContext)
287 return false;
289 // verify message digest
290 if (messageDigest == null)
291 return false;
292 if (!messageDigest.CompareValue (calculatedMessageDigest))
293 return false;
295 // verify signature
296 string hashOID = CryptoConfig.MapNameToOID (ha.ToString ());
298 // change to SET OF (not [0]) as per PKCS #7 1.5
299 ASN1 aa = new ASN1 (0x31);
300 foreach (ASN1 a in sd.SignerInfo.AuthenticatedAttributes)
301 aa.Add (a);
302 ha.Initialize ();
303 byte[] p7hash = ha.ComputeHash (aa.GetBytes ());
305 byte[] signature = sd.SignerInfo.Signature;
306 // we need to find the specified certificate
307 string issuer = sd.SignerInfo.IssuerName;
308 byte[] serial = sd.SignerInfo.SerialNumber;
309 foreach (X509Certificate x509 in coll) {
310 if (CompareIssuerSerial (issuer, serial, x509)) {
311 // don't verify is key size don't match
312 if (x509.PublicKey.Length > (signature.Length >> 3)) {
313 // return the signing certificate even if the signature isn't correct
314 // (required behaviour for 2.0 support)
315 signingCertificate = x509;
316 RSACryptoServiceProvider rsa = (RSACryptoServiceProvider) x509.RSA;
317 if (rsa.VerifyHash (p7hash, hashOID, signature)) {
318 signerChain.LoadCertificates (coll);
319 trustedRoot = signerChain.Build (x509);
320 break;
326 // timestamp signature is optional
327 if (sd.SignerInfo.UnauthenticatedAttributes.Count == 0) {
328 trustedTimestampRoot = true;
329 } else {
330 for (int i = 0; i < sd.SignerInfo.UnauthenticatedAttributes.Count; i++) {
331 ASN1 attr = (ASN1) sd.SignerInfo.UnauthenticatedAttributes[i];
332 string oid = ASN1Convert.ToOid (attr[0]);
333 switch (oid) {
334 case PKCS7.Oid.countersignature:
335 // SEQUENCE {
336 // OBJECT IDENTIFIER
337 // countersignature (1 2 840 113549 1 9 6)
338 // SET {
339 PKCS7.SignerInfo cs = new PKCS7.SignerInfo (attr[1]);
340 trustedTimestampRoot = VerifyCounterSignature (cs, signature);
341 break;
342 default:
343 // we don't support other unauthenticated attributes
344 break;
349 return (trustedRoot && trustedTimestampRoot);
352 private bool VerifyCounterSignature (PKCS7.SignerInfo cs, byte[] signature)
354 // SEQUENCE {
355 // INTEGER 1
356 if (cs.Version != 1)
357 return false;
358 // SEQUENCE {
359 // SEQUENCE {
361 string contentType = null;
362 ASN1 messageDigest = null;
363 for (int i=0; i < cs.AuthenticatedAttributes.Count; i++) {
364 // SEQUENCE {
365 // OBJECT IDENTIFIER
366 ASN1 attr = (ASN1) cs.AuthenticatedAttributes [i];
367 string oid = ASN1Convert.ToOid (attr[0]);
368 switch (oid) {
369 case "1.2.840.113549.1.9.3":
370 // contentType
371 contentType = ASN1Convert.ToOid (attr[1][0]);
372 break;
373 case "1.2.840.113549.1.9.4":
374 // messageDigest
375 messageDigest = attr[1][0];
376 break;
377 case "1.2.840.113549.1.9.5":
378 // SEQUENCE {
379 // OBJECT IDENTIFIER
380 // signingTime (1 2 840 113549 1 9 5)
381 // SET {
382 // UTCTime '030124013651Z'
383 // }
384 // }
385 timestamp = ASN1Convert.ToDateTime (attr[1][0]);
386 break;
387 default:
388 break;
392 if (contentType != PKCS7.Oid.data)
393 return false;
395 // verify message digest
396 if (messageDigest == null)
397 return false;
398 // TODO: must be read from the ASN.1 structure
399 string hashName = null;
400 switch (messageDigest.Length) {
401 case 16:
402 hashName = "MD5";
403 break;
404 case 20:
405 hashName = "SHA1";
406 break;
408 HashAlgorithm ha = HashAlgorithm.Create (hashName);
409 if (!messageDigest.CompareValue (ha.ComputeHash (signature)))
410 return false;
412 // verify signature
413 byte[] counterSignature = cs.Signature;
415 // change to SET OF (not [0]) as per PKCS #7 1.5
416 ASN1 aa = new ASN1 (0x31);
417 foreach (ASN1 a in cs.AuthenticatedAttributes)
418 aa.Add (a);
419 byte[] p7hash = ha.ComputeHash (aa.GetBytes ());
421 // we need to try all certificates
422 string issuer = cs.IssuerName;
423 byte[] serial = cs.SerialNumber;
424 foreach (X509Certificate x509 in coll) {
425 if (CompareIssuerSerial (issuer, serial, x509)) {
426 if (x509.PublicKey.Length > counterSignature.Length) {
427 RSACryptoServiceProvider rsa = (RSACryptoServiceProvider) x509.RSA;
428 // we need to HACK around bad (PKCS#1 1.5) signatures made by Verisign Timestamp Service
429 // and this means copying stuff into our own RSAManaged to get the required flexibility
430 RSAManaged rsam = new RSAManaged ();
431 rsam.ImportParameters (rsa.ExportParameters (false));
432 if (PKCS1.Verify_v15 (rsam, ha, p7hash, counterSignature, true)) {
433 timestampChain.LoadCertificates (coll);
434 return (timestampChain.Build (x509));
439 // no certificate can verify this signature!
440 return false;
443 private void Reset ()
445 filename = null;
446 entry = null;
447 hash = null;
448 signedHash = null;
449 signingCertificate = null;
450 reason = -1;
451 trustedRoot = false;
452 trustedTimestampRoot = false;
453 signerChain.Reset ();
454 timestampChain.Reset ();
455 timestamp = DateTime.MinValue;
460 #endif