2 // AuthenticodeDeformatter.cs: Authenticode signature validator
5 // Sebastien Pouliot (spouliot@motus.com)
7 // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
11 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35 using System.Runtime.InteropServices;
36 using System.Security.Cryptography;
39 using Mono.Security.X509;
41 namespace Mono.Security.Authenticode {
44 // a. http://www.cs.auckland.ac.nz/~pgut001/pubs/authenticode.txt
51 class AuthenticodeDeformatter : AuthenticodeBase {
53 private string filename;
55 private X509CertificateCollection coll;
56 private ASN1 signedHash;
57 private DateTime timestamp;
58 private X509Certificate signingCertificate;
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 ()
73 if (!CheckSignature (fileName)) {
74 // invalid or no signature
75 if (signedHash != null)
76 throw new COMException ("Invalid signature");
77 // no exception is thrown when there's no signature in the PE file
81 public string FileName {
82 get { return filename; }
83 set { CheckSignature (value); }
88 if (signedHash == null)
90 return (byte[]) signedHash.Value.Clone ();
102 public bool IsTrusted ()
104 if (rawData == null) {
109 if (signingCertificate == null) {
114 if (signerChain.Root == null) {
119 if (timestamp != DateTime.MinValue) {
120 if (timestampChain.Root == null) {
125 // check that file was timestamped when certificates were valid
126 if (!signingCertificate.WasCurrent (Timestamp)) {
131 else if (!signingCertificate.IsCurrent) {
132 // signature only valid if the certificate is valid
141 public byte[] Signature {
142 get { return (byte[]) rawData.Clone (); }
145 public DateTime Timestamp {
146 get { return timestamp; }
149 public X509CertificateCollection Certificates {
153 public X509Certificate SigningCertificate {
154 get { return signingCertificate; }
157 private bool CheckSignature (string fileName)
161 // by default we try with MD5
162 string hashName = "MD5";
163 // compare the signature's hash with the hash of the file
164 hash = HashFile (filename, hashName);
166 // is a signature present ?
170 PKCS7.ContentInfo ci = new PKCS7.ContentInfo (rawData);
171 if (ci.ContentType != PKCS7.Oid.signedData)
174 PKCS7.SignedData sd = new PKCS7.SignedData (ci.Content);
175 if (sd.ContentInfo.ContentType != spcIndirectDataContext)
178 coll = sd.Certificates;
180 ASN1 spc = sd.ContentInfo.Content;
181 signedHash = spc [0][1][1];
182 if (signedHash.Length == 20) {
183 // seems to be SHA-1, restart hashing
185 hash = HashFile (filename, hashName);
188 if (!signedHash.CompareValue (hash))
191 // messageDigest is a hash of spcIndirectDataContext (which includes the file hash)
192 byte[] spcIDC = spc [0].Value;
193 HashAlgorithm ha = HashAlgorithm.Create (hashName);
194 byte[] messageDigest = ha.ComputeHash (spcIDC);
196 return VerifySignature (sd, messageDigest, hashName);
199 private bool CompareIssuerSerial (string issuer, byte[] serial, X509Certificate x509)
201 if (issuer != x509.IssuerName)
203 if (serial.Length != x509.SerialNumber.Length)
205 // MS shows the serial number inversed (so is Mono.Security.X509.X509Certificate)
206 int n = serial.Length;
207 for (int i=0; i < serial.Length; i++) {
208 if (serial [i] != x509.SerialNumber [--n])
215 //private bool VerifySignature (ASN1 cs, byte[] calculatedMessageDigest, string hashName)
216 private bool VerifySignature (PKCS7.SignedData sd, byte[] calculatedMessageDigest, string hashName)
218 string contentType = null;
219 ASN1 messageDigest = null;
220 string spcStatementType = null;
221 string spcSpOpusInfo = null;
223 for (int i=0; i < sd.SignerInfo.AuthenticatedAttributes.Count; i++) {
224 ASN1 attr = (ASN1) sd.SignerInfo.AuthenticatedAttributes [i];
225 string oid = ASN1Convert.ToOid (attr[0]);
227 case "1.2.840.113549.1.9.3":
229 contentType = ASN1Convert.ToOid (attr[1][0]);
231 case "1.2.840.113549.1.9.4":
233 messageDigest = attr[1][0];
235 case "1.3.6.1.4.1.311.2.1.11":
236 // spcStatementType (Microsoft code signing)
238 // - individualCodeSigning (1 3 6 1 4 1 311 2 1 21)
239 // - commercialCodeSigning (1 3 6 1 4 1 311 2 1 22)
240 spcStatementType = ASN1Convert.ToOid (attr[1][0][0]);
242 case "1.3.6.1.4.1.311.2.1.12":
243 // spcSpOpusInfo (Microsoft code signing)
245 spcSpOpusInfo = System.Text.Encoding.UTF8.GetString (attr[1][0][1][0].Value);
247 catch (NullReferenceException) {
248 spcSpOpusInfo = null;
255 if (contentType != spcIndirectDataContext)
258 // verify message digest
259 if (messageDigest == null)
261 if (!messageDigest.CompareValue (calculatedMessageDigest))
265 string hashOID = CryptoConfig.MapNameToOID (hashName);
267 // change to SET OF (not [0]) as per PKCS #7 1.5
268 ASN1 aa = new ASN1 (0x31);
269 foreach (ASN1 a in sd.SignerInfo.AuthenticatedAttributes)
271 HashAlgorithm ha = HashAlgorithm.Create (hashName);
272 byte[] p7hash = ha.ComputeHash (aa.GetBytes ());
274 byte[] signature = sd.SignerInfo.Signature;
275 // we need to find the specified certificate
276 string issuer = sd.SignerInfo.IssuerName;
277 byte[] serial = sd.SignerInfo.SerialNumber;
278 foreach (X509Certificate x509 in coll) {
279 if (CompareIssuerSerial (issuer, serial, x509)) {
280 // don't verify is key size don't match
281 if (x509.PublicKey.Length > (signature.Length >> 3)) {
282 RSACryptoServiceProvider rsa = (RSACryptoServiceProvider) x509.RSA;
283 if (rsa.VerifyHash (p7hash, hashOID, signature)) {
284 signerChain.LoadCertificates (coll);
285 if (signerChain.Build (x509))
286 signingCertificate = x509;
294 for (int i=0; i < sd.SignerInfo.UnauthenticatedAttributes.Count; i++) {
295 ASN1 attr = (ASN1) sd.SignerInfo.UnauthenticatedAttributes [i];
296 string oid = ASN1Convert.ToOid (attr [0]);
298 case PKCS7.Oid.countersignature:
301 // countersignature (1 2 840 113549 1 9 6)
303 PKCS7.SignerInfo cs = new PKCS7.SignerInfo (attr [1]);
304 return VerifyCounterSignature (cs, signature, hashName);
306 // we don't support other unauthenticated attributes
314 private bool VerifyCounterSignature (PKCS7.SignerInfo cs, byte[] signature, string hashName)
323 string contentType = null;
324 ASN1 messageDigest = null;
325 for (int i=0; i < cs.AuthenticatedAttributes.Count; i++) {
328 ASN1 attr = (ASN1) cs.AuthenticatedAttributes [i];
329 string oid = ASN1Convert.ToOid (attr[0]);
331 case "1.2.840.113549.1.9.3":
333 contentType = ASN1Convert.ToOid (attr[1][0]);
335 case "1.2.840.113549.1.9.4":
337 messageDigest = attr[1][0];
339 case "1.2.840.113549.1.9.5":
342 // signingTime (1 2 840 113549 1 9 5)
344 // UTCTime '030124013651Z'
347 timestamp = ASN1Convert.ToDateTime (attr[1][0]);
354 if (contentType != PKCS7.Oid.data)
357 // verify message digest
358 if (messageDigest == null)
360 // TODO: must be read from the ASN.1 structure
361 switch (messageDigest.Length) {
369 HashAlgorithm ha = HashAlgorithm.Create (hashName);
370 if (!messageDigest.CompareValue (ha.ComputeHash (signature)))
374 byte[] counterSignature = cs.Signature;
375 string hashOID = CryptoConfig.MapNameToOID (hashName);
377 // change to SET OF (not [0]) as per PKCS #7 1.5
378 ASN1 aa = new ASN1 (0x31);
379 foreach (ASN1 a in cs.AuthenticatedAttributes)
381 byte[] p7hash = ha.ComputeHash (aa.GetBytes ());
383 // we need to try all certificates
384 string issuer = cs.IssuerName;
385 byte[] serial = cs.SerialNumber;
386 foreach (X509Certificate x509 in coll) {
387 if (CompareIssuerSerial (issuer, serial, x509)) {
388 // don't verify if key size don't match
389 if (x509.PublicKey.Length > (counterSignature.Length >> 3)) {
390 RSACryptoServiceProvider rsa = (RSACryptoServiceProvider) x509.RSA;
391 if (rsa.VerifyHash (p7hash, hashOID, counterSignature)) {
392 timestampChain.LoadCertificates (coll);
393 return (timestampChain.Build (x509));
398 // no certificate can verify this signature!