Merge pull request #645 from knocte/nitpicks
[mono-project.git] / mcs / class / corlib / Mono.Security.Authenticode / AuthenticodeDeformatter.cs
bloba92affc4e549f169a97632f952654b38da513670
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 using System;
31 using System.IO;
32 using System.Runtime.InteropServices;
33 using System.Security;
34 using System.Security.Cryptography;
36 using Mono.Security.Cryptography;
37 using Mono.Security.X509;
39 namespace Mono.Security.Authenticode {
41 // References:
42 // a. http://www.cs.auckland.ac.nz/~pgut001/pubs/authenticode.txt
44 #if INSIDE_CORLIB
45 internal
46 #else
47 public
48 #endif
49 class AuthenticodeDeformatter : AuthenticodeBase {
51 private string filename;
52 private byte[] hash;
53 private X509CertificateCollection coll;
54 private ASN1 signedHash;
55 private DateTime timestamp;
56 private X509Certificate signingCertificate;
57 private int reason;
58 private bool trustedRoot;
59 private bool trustedTimestampRoot;
60 private byte[] entry;
62 private X509Chain signerChain;
63 private X509Chain timestampChain;
65 public AuthenticodeDeformatter () : base ()
67 reason = -1;
68 signerChain = new X509Chain ();
69 timestampChain = new X509Chain ();
72 public AuthenticodeDeformatter (string fileName) : this ()
74 FileName = fileName;
77 public string FileName {
78 get { return filename; }
79 set {
80 Reset ();
81 try {
82 CheckSignature (value);
84 catch (SecurityException) {
85 throw;
87 catch (Exception) {
88 reason = 1;
93 public byte[] Hash {
94 get {
95 if (signedHash == null)
96 return null;
97 return (byte[]) signedHash.Value.Clone ();
101 public int Reason {
102 get {
103 if (reason == -1)
104 IsTrusted ();
105 return reason;
109 public bool IsTrusted ()
111 if (entry == null) {
112 reason = 1;
113 return false;
116 if (signingCertificate == null) {
117 reason = 7;
118 return false;
121 if ((signerChain.Root == null) || !trustedRoot) {
122 reason = 6;
123 return false;
126 if (timestamp != DateTime.MinValue) {
127 if ((timestampChain.Root == null) || !trustedTimestampRoot) {
128 reason = 6;
129 return false;
132 // check that file was timestamped when certificates were valid
133 if (!signingCertificate.WasCurrent (Timestamp)) {
134 reason = 4;
135 return false;
138 else if (!signingCertificate.IsCurrent) {
139 // signature only valid if the certificate is valid
140 reason = 8;
141 return false;
144 if (reason == -1)
145 reason = 0;
146 return true;
149 public byte[] Signature {
150 get {
151 if (entry == null)
152 return null;
153 return (byte[]) entry.Clone ();
157 public DateTime Timestamp {
158 get { return timestamp; }
161 public X509CertificateCollection Certificates {
162 get { return coll; }
165 public X509Certificate SigningCertificate {
166 get { return signingCertificate; }
169 private bool CheckSignature (string fileName)
171 filename = fileName;
172 Open (filename);
173 entry = GetSecurityEntry ();
174 if (entry == null) {
175 // no signature is present
176 reason = 1;
177 Close ();
178 return false;
181 PKCS7.ContentInfo ci = new PKCS7.ContentInfo (entry);
182 if (ci.ContentType != PKCS7.Oid.signedData) {
183 Close ();
184 return false;
187 PKCS7.SignedData sd = new PKCS7.SignedData (ci.Content);
188 if (sd.ContentInfo.ContentType != spcIndirectDataContext) {
189 Close ();
190 return false;
193 coll = sd.Certificates;
195 ASN1 spc = sd.ContentInfo.Content;
196 signedHash = spc [0][1][1];
198 HashAlgorithm ha = null;
199 switch (signedHash.Length) {
200 case 16:
201 ha = HashAlgorithm.Create ("MD5");
202 hash = GetHash (ha);
203 break;
204 case 20:
205 ha = HashAlgorithm.Create ("SHA1");
206 hash = GetHash (ha);
207 break;
208 default:
209 reason = 5;
210 Close ();
211 return false;
213 Close ();
215 if (!signedHash.CompareValue (hash)) {
216 reason = 2;
219 // messageDigest is a hash of spcIndirectDataContext (which includes the file hash)
220 byte[] spcIDC = spc [0].Value;
221 ha.Initialize (); // re-using hash instance
222 byte[] messageDigest = ha.ComputeHash (spcIDC);
224 bool sign = VerifySignature (sd, messageDigest, ha);
225 return (sign && (reason == 0));
228 private bool CompareIssuerSerial (string issuer, byte[] serial, X509Certificate x509)
230 if (issuer != x509.IssuerName)
231 return false;
232 if (serial.Length != x509.SerialNumber.Length)
233 return false;
234 // MS shows the serial number inversed (so is Mono.Security.X509.X509Certificate)
235 int n = serial.Length;
236 for (int i=0; i < serial.Length; i++) {
237 if (serial [i] != x509.SerialNumber [--n])
238 return false;
240 // must be true
241 return true;
244 //private bool VerifySignature (ASN1 cs, byte[] calculatedMessageDigest, string hashName)
245 private bool VerifySignature (PKCS7.SignedData sd, byte[] calculatedMessageDigest, HashAlgorithm ha)
247 string contentType = null;
248 ASN1 messageDigest = null;
249 // string spcStatementType = null;
250 // string spcSpOpusInfo = null;
252 for (int i=0; i < sd.SignerInfo.AuthenticatedAttributes.Count; i++) {
253 ASN1 attr = (ASN1) sd.SignerInfo.AuthenticatedAttributes [i];
254 string oid = ASN1Convert.ToOid (attr[0]);
255 switch (oid) {
256 case "1.2.840.113549.1.9.3":
257 // contentType
258 contentType = ASN1Convert.ToOid (attr[1][0]);
259 break;
260 case "1.2.840.113549.1.9.4":
261 // messageDigest
262 messageDigest = attr[1][0];
263 break;
264 case "1.3.6.1.4.1.311.2.1.11":
265 // spcStatementType (Microsoft code signing)
266 // possible values
267 // - individualCodeSigning (1 3 6 1 4 1 311 2 1 21)
268 // - commercialCodeSigning (1 3 6 1 4 1 311 2 1 22)
269 // spcStatementType = ASN1Convert.ToOid (attr[1][0][0]);
270 break;
271 case "1.3.6.1.4.1.311.2.1.12":
272 // spcSpOpusInfo (Microsoft code signing)
273 /* try {
274 spcSpOpusInfo = System.Text.Encoding.UTF8.GetString (attr[1][0][0][0].Value);
276 catch (NullReferenceException) {
277 spcSpOpusInfo = null;
279 break;
280 default:
281 break;
284 if (contentType != spcIndirectDataContext)
285 return false;
287 // verify message digest
288 if (messageDigest == null)
289 return false;
290 if (!messageDigest.CompareValue (calculatedMessageDigest))
291 return false;
293 // verify signature
294 string hashOID = CryptoConfig.MapNameToOID (ha.ToString ());
296 // change to SET OF (not [0]) as per PKCS #7 1.5
297 ASN1 aa = new ASN1 (0x31);
298 foreach (ASN1 a in sd.SignerInfo.AuthenticatedAttributes)
299 aa.Add (a);
300 ha.Initialize ();
301 byte[] p7hash = ha.ComputeHash (aa.GetBytes ());
303 byte[] signature = sd.SignerInfo.Signature;
304 // we need to find the specified certificate
305 string issuer = sd.SignerInfo.IssuerName;
306 byte[] serial = sd.SignerInfo.SerialNumber;
307 foreach (X509Certificate x509 in coll) {
308 if (CompareIssuerSerial (issuer, serial, x509)) {
309 // don't verify is key size don't match
310 if (x509.PublicKey.Length > (signature.Length >> 3)) {
311 // return the signing certificate even if the signature isn't correct
312 // (required behaviour for 2.0 support)
313 signingCertificate = x509;
314 RSACryptoServiceProvider rsa = (RSACryptoServiceProvider) x509.RSA;
315 if (rsa.VerifyHash (p7hash, hashOID, signature)) {
316 signerChain.LoadCertificates (coll);
317 trustedRoot = signerChain.Build (x509);
318 break;
324 // timestamp signature is optional
325 if (sd.SignerInfo.UnauthenticatedAttributes.Count == 0) {
326 trustedTimestampRoot = true;
327 } else {
328 for (int i = 0; i < sd.SignerInfo.UnauthenticatedAttributes.Count; i++) {
329 ASN1 attr = (ASN1) sd.SignerInfo.UnauthenticatedAttributes[i];
330 string oid = ASN1Convert.ToOid (attr[0]);
331 switch (oid) {
332 case PKCS7.Oid.countersignature:
333 // SEQUENCE {
334 // OBJECT IDENTIFIER
335 // countersignature (1 2 840 113549 1 9 6)
336 // SET {
337 PKCS7.SignerInfo cs = new PKCS7.SignerInfo (attr[1]);
338 trustedTimestampRoot = VerifyCounterSignature (cs, signature);
339 break;
340 default:
341 // we don't support other unauthenticated attributes
342 break;
347 return (trustedRoot && trustedTimestampRoot);
350 private bool VerifyCounterSignature (PKCS7.SignerInfo cs, byte[] signature)
352 // SEQUENCE {
353 // INTEGER 1
354 if (cs.Version != 1)
355 return false;
356 // SEQUENCE {
357 // SEQUENCE {
359 string contentType = null;
360 ASN1 messageDigest = null;
361 for (int i=0; i < cs.AuthenticatedAttributes.Count; i++) {
362 // SEQUENCE {
363 // OBJECT IDENTIFIER
364 ASN1 attr = (ASN1) cs.AuthenticatedAttributes [i];
365 string oid = ASN1Convert.ToOid (attr[0]);
366 switch (oid) {
367 case "1.2.840.113549.1.9.3":
368 // contentType
369 contentType = ASN1Convert.ToOid (attr[1][0]);
370 break;
371 case "1.2.840.113549.1.9.4":
372 // messageDigest
373 messageDigest = attr[1][0];
374 break;
375 case "1.2.840.113549.1.9.5":
376 // SEQUENCE {
377 // OBJECT IDENTIFIER
378 // signingTime (1 2 840 113549 1 9 5)
379 // SET {
380 // UTCTime '030124013651Z'
381 // }
382 // }
383 timestamp = ASN1Convert.ToDateTime (attr[1][0]);
384 break;
385 default:
386 break;
390 if (contentType != PKCS7.Oid.data)
391 return false;
393 // verify message digest
394 if (messageDigest == null)
395 return false;
396 // TODO: must be read from the ASN.1 structure
397 string hashName = null;
398 switch (messageDigest.Length) {
399 case 16:
400 hashName = "MD5";
401 break;
402 case 20:
403 hashName = "SHA1";
404 break;
406 HashAlgorithm ha = HashAlgorithm.Create (hashName);
407 if (!messageDigest.CompareValue (ha.ComputeHash (signature)))
408 return false;
410 // verify signature
411 byte[] counterSignature = cs.Signature;
413 // change to SET OF (not [0]) as per PKCS #7 1.5
414 ASN1 aa = new ASN1 (0x31);
415 foreach (ASN1 a in cs.AuthenticatedAttributes)
416 aa.Add (a);
417 byte[] p7hash = ha.ComputeHash (aa.GetBytes ());
419 // we need to try all certificates
420 string issuer = cs.IssuerName;
421 byte[] serial = cs.SerialNumber;
422 foreach (X509Certificate x509 in coll) {
423 if (CompareIssuerSerial (issuer, serial, x509)) {
424 if (x509.PublicKey.Length > counterSignature.Length) {
425 RSACryptoServiceProvider rsa = (RSACryptoServiceProvider) x509.RSA;
426 // we need to HACK around bad (PKCS#1 1.5) signatures made by Verisign Timestamp Service
427 // and this means copying stuff into our own RSAManaged to get the required flexibility
428 RSAManaged rsam = new RSAManaged ();
429 rsam.ImportParameters (rsa.ExportParameters (false));
430 if (PKCS1.Verify_v15 (rsam, ha, p7hash, counterSignature, true)) {
431 timestampChain.LoadCertificates (coll);
432 return (timestampChain.Build (x509));
437 // no certificate can verify this signature!
438 return false;
441 private void Reset ()
443 filename = null;
444 entry = null;
445 hash = null;
446 signedHash = null;
447 signingCertificate = null;
448 reason = -1;
449 trustedRoot = false;
450 trustedTimestampRoot = false;
451 signerChain.Reset ();
452 timestampChain.Reset ();
453 timestamp = DateTime.MinValue;