2 // AuthenticodeDeformatter.cs: Authenticode signature validator
5 // Sebastien Pouliot <sebastien@ximian.com>
7 // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
8 // Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
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:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
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.
32 using System.Runtime.InteropServices;
33 using System.Security;
34 using System.Security.Cryptography;
36 using Mono.Security.X509;
38 namespace Mono.Security.Authenticode {
41 // a. http://www.cs.auckland.ac.nz/~pgut001/pubs/authenticode.txt
48 class AuthenticodeDeformatter : AuthenticodeBase {
50 private string filename;
52 private X509CertificateCollection coll;
53 private ASN1 signedHash;
54 private DateTime timestamp;
55 private X509Certificate signingCertificate;
57 private bool trustedRoot;
58 private bool trustedTimestampRoot;
61 private X509Chain signerChain;
62 private X509Chain timestampChain;
64 public AuthenticodeDeformatter () : base ()
67 signerChain = new X509Chain ();
68 timestampChain = new X509Chain ();
71 public AuthenticodeDeformatter (string fileName) : this ()
76 public string FileName {
77 get { return filename; }
81 CheckSignature (value);
83 catch (SecurityException) {
94 if (signedHash == null)
96 return (byte[]) signedHash.Value.Clone ();
108 public bool IsTrusted ()
115 if (signingCertificate == null) {
120 if ((signerChain.Root == null) || !trustedRoot) {
125 if (timestamp != DateTime.MinValue) {
126 if ((timestampChain.Root == null) || !trustedTimestampRoot) {
131 // check that file was timestamped when certificates were valid
132 if (!signingCertificate.WasCurrent (Timestamp)) {
137 else if (!signingCertificate.IsCurrent) {
138 // signature only valid if the certificate is valid
147 public byte[] Signature {
151 return (byte[]) entry.Clone ();
155 public DateTime Timestamp {
156 get { return timestamp; }
159 public X509CertificateCollection Certificates {
163 public X509Certificate SigningCertificate {
164 get { return signingCertificate; }
167 private bool CheckSignature (string fileName)
170 base.Open (filename);
171 entry = base.GetSecurityEntry ();
173 // no signature is present
179 PKCS7.ContentInfo ci = new PKCS7.ContentInfo (entry);
180 if (ci.ContentType != PKCS7.Oid.signedData) {
185 PKCS7.SignedData sd = new PKCS7.SignedData (ci.Content);
186 if (sd.ContentInfo.ContentType != spcIndirectDataContext) {
191 coll = sd.Certificates;
193 ASN1 spc = sd.ContentInfo.Content;
194 signedHash = spc [0][1][1];
196 HashAlgorithm ha = null;
197 switch (signedHash.Length) {
199 ha = HashAlgorithm.Create ("MD5");
203 ha = HashAlgorithm.Create ("SHA1");
213 if (!signedHash.CompareValue (hash)) {
217 // messageDigest is a hash of spcIndirectDataContext (which includes the file hash)
218 byte[] spcIDC = spc [0].Value;
219 ha.Initialize (); // re-using hash instance
220 byte[] messageDigest = ha.ComputeHash (spcIDC);
222 bool sign = VerifySignature (sd, messageDigest, ha);
223 return (sign && (reason == 0));
226 private bool CompareIssuerSerial (string issuer, byte[] serial, X509Certificate x509)
228 if (issuer != x509.IssuerName)
230 if (serial.Length != x509.SerialNumber.Length)
232 // MS shows the serial number inversed (so is Mono.Security.X509.X509Certificate)
233 int n = serial.Length;
234 for (int i=0; i < serial.Length; i++) {
235 if (serial [i] != x509.SerialNumber [--n])
242 //private bool VerifySignature (ASN1 cs, byte[] calculatedMessageDigest, string hashName)
243 private bool VerifySignature (PKCS7.SignedData sd, byte[] calculatedMessageDigest, HashAlgorithm ha)
245 string contentType = null;
246 ASN1 messageDigest = null;
247 // string spcStatementType = null;
248 // string spcSpOpusInfo = null;
250 for (int i=0; i < sd.SignerInfo.AuthenticatedAttributes.Count; i++) {
251 ASN1 attr = (ASN1) sd.SignerInfo.AuthenticatedAttributes [i];
252 string oid = ASN1Convert.ToOid (attr[0]);
254 case "1.2.840.113549.1.9.3":
256 contentType = ASN1Convert.ToOid (attr[1][0]);
258 case "1.2.840.113549.1.9.4":
260 messageDigest = attr[1][0];
262 case "1.3.6.1.4.1.311.2.1.11":
263 // spcStatementType (Microsoft code signing)
265 // - individualCodeSigning (1 3 6 1 4 1 311 2 1 21)
266 // - commercialCodeSigning (1 3 6 1 4 1 311 2 1 22)
267 // spcStatementType = ASN1Convert.ToOid (attr[1][0][0]);
269 case "1.3.6.1.4.1.311.2.1.12":
270 // spcSpOpusInfo (Microsoft code signing)
272 spcSpOpusInfo = System.Text.Encoding.UTF8.GetString (attr[1][0][0][0].Value);
274 catch (NullReferenceException) {
275 spcSpOpusInfo = null;
282 if (contentType != spcIndirectDataContext)
285 // verify message digest
286 if (messageDigest == null)
288 if (!messageDigest.CompareValue (calculatedMessageDigest))
292 string hashOID = CryptoConfig.MapNameToOID (ha.ToString ());
294 // change to SET OF (not [0]) as per PKCS #7 1.5
295 ASN1 aa = new ASN1 (0x31);
296 foreach (ASN1 a in sd.SignerInfo.AuthenticatedAttributes)
299 byte[] p7hash = ha.ComputeHash (aa.GetBytes ());
301 byte[] signature = sd.SignerInfo.Signature;
302 // we need to find the specified certificate
303 string issuer = sd.SignerInfo.IssuerName;
304 byte[] serial = sd.SignerInfo.SerialNumber;
305 foreach (X509Certificate x509 in coll) {
306 if (CompareIssuerSerial (issuer, serial, x509)) {
307 // don't verify is key size don't match
308 if (x509.PublicKey.Length > (signature.Length >> 3)) {
309 // return the signing certificate even if the signature isn't correct
310 // (required behaviour for 2.0 support)
311 signingCertificate = x509;
312 RSACryptoServiceProvider rsa = (RSACryptoServiceProvider) x509.RSA;
313 if (rsa.VerifyHash (p7hash, hashOID, signature)) {
314 signerChain.LoadCertificates (coll);
315 trustedRoot = signerChain.Build (x509);
322 for (int i=0; i < sd.SignerInfo.UnauthenticatedAttributes.Count; i++) {
323 ASN1 attr = (ASN1) sd.SignerInfo.UnauthenticatedAttributes [i];
324 string oid = ASN1Convert.ToOid (attr [0]);
326 case PKCS7.Oid.countersignature:
329 // countersignature (1 2 840 113549 1 9 6)
331 PKCS7.SignerInfo cs = new PKCS7.SignerInfo (attr [1]);
332 trustedTimestampRoot = VerifyCounterSignature (cs, signature);
335 // we don't support other unauthenticated attributes
340 return (trustedRoot && trustedTimestampRoot);
343 private bool VerifyCounterSignature (PKCS7.SignerInfo cs, byte[] signature)
352 string contentType = null;
353 ASN1 messageDigest = null;
354 for (int i=0; i < cs.AuthenticatedAttributes.Count; i++) {
357 ASN1 attr = (ASN1) cs.AuthenticatedAttributes [i];
358 string oid = ASN1Convert.ToOid (attr[0]);
360 case "1.2.840.113549.1.9.3":
362 contentType = ASN1Convert.ToOid (attr[1][0]);
364 case "1.2.840.113549.1.9.4":
366 messageDigest = attr[1][0];
368 case "1.2.840.113549.1.9.5":
371 // signingTime (1 2 840 113549 1 9 5)
373 // UTCTime '030124013651Z'
376 timestamp = ASN1Convert.ToDateTime (attr[1][0]);
383 if (contentType != PKCS7.Oid.data)
386 // verify message digest
387 if (messageDigest == null)
389 // TODO: must be read from the ASN.1 structure
390 string hashName = null;
391 switch (messageDigest.Length) {
399 HashAlgorithm ha = HashAlgorithm.Create (hashName);
400 if (!messageDigest.CompareValue (ha.ComputeHash (signature)))
404 byte[] counterSignature = cs.Signature;
405 string hashOID = CryptoConfig.MapNameToOID (hashName);
407 // change to SET OF (not [0]) as per PKCS #7 1.5
408 ASN1 aa = new ASN1 (0x31);
409 foreach (ASN1 a in cs.AuthenticatedAttributes)
411 byte[] p7hash = ha.ComputeHash (aa.GetBytes ());
413 // we need to try all certificates
414 string issuer = cs.IssuerName;
415 byte[] serial = cs.SerialNumber;
416 foreach (X509Certificate x509 in coll) {
417 if (CompareIssuerSerial (issuer, serial, x509)) {
418 // don't verify if key size don't match
419 if (x509.PublicKey.Length > (counterSignature.Length >> 3)) {
420 RSACryptoServiceProvider rsa = (RSACryptoServiceProvider) x509.RSA;
421 if (rsa.VerifyHash (p7hash, hashOID, counterSignature)) {
422 timestampChain.LoadCertificates (coll);
423 return (timestampChain.Build (x509));
428 // no certificate can verify this signature!
432 private void Reset ()
438 signingCertificate = null;
441 trustedTimestampRoot = false;
442 signerChain.Reset ();
443 timestampChain.Reset ();
444 timestamp = DateTime.MinValue;