2 // AuthenticodeSignature.cs: Authenticode signature validator and generator
5 // Sebastien Pouliot (spouliot@motus.com)
7 // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
12 using System.Security.Cryptography;
15 using Mono.Security.X509;
17 namespace Mono.Security.Authenticode {
20 // a. http://www.cs.auckland.ac.nz/~pgut001/pubs/authenticode.txt
22 public class AuthenticodeDeformatter : AuthenticodeBase {
24 private string filename;
26 private X509CertificateCollection coll;
27 private ASN1 signedHash;
28 private DateTime timestamp;
29 private X509Certificate signingCertificate;
32 private X509Chain signerChain;
33 private X509Chain timestampChain;
35 public AuthenticodeDeformatter () : base ()
38 signerChain = new X509Chain ();
39 timestampChain = new X509Chain ();
42 public AuthenticodeDeformatter (string fileName) : this ()
44 CheckSignature (fileName);
47 public string FileName {
48 get { return filename; }
49 set { CheckSignature (value); }
54 if (signedHash == null)
56 return signedHash.Value;
68 public bool IsTrusted ()
70 if (rawData == null) {
75 if (signingCertificate == null) {
80 if (signerChain.Root == null) {
85 if (timestamp != DateTime.MinValue) {
86 if (timestampChain.Root == null) {
91 // check that file was timestamped when certificates were valid
92 if (!signingCertificate.WasCurrent (Timestamp)) {
97 else if (!signingCertificate.IsCurrent) {
98 // signature only valid if the certificate is valid
107 public byte[] Signature {
108 get { return rawData; }
111 public DateTime Timestamp {
112 get { return timestamp; }
115 public X509CertificateCollection Certificates {
119 private bool CheckSignature (string fileName)
123 // by default we try with MD5
124 string hashName = "MD5";
125 // compare the signature's hash with the hash of the file
126 hash = HashFile (filename, hashName);
128 // is a signature present ?
132 PKCS7.ContentInfo ci = new PKCS7.ContentInfo (rawData);
133 if (ci.ContentType != PKCS7.signedData)
136 PKCS7.SignedData sd = new PKCS7.SignedData (ci.Content);
137 if (sd.ContentInfo.ContentType != spcIndirectDataContext)
140 coll = sd.Certificates;
142 ASN1 spc = sd.ContentInfo.Content;
143 signedHash = spc [0][1][1];
144 if (signedHash.Length == 20) {
145 // seems to be SHA-1, restart hashing
147 hash = HashFile (filename, hashName);
150 if (!signedHash.CompareValue (hash))
153 // messageDigest is a hash of spcIndirectDataContext (which includes the file hash)
154 byte[] spcIDC = spc [0].Value;
155 HashAlgorithm ha = HashAlgorithm.Create (hashName);
156 byte[] messageDigest = ha.ComputeHash (spcIDC);
158 return VerifySignature (sd, messageDigest, hashName);
161 private bool CompareIssuerSerial (string issuer, byte[] serial, X509Certificate x509)
163 if (issuer != x509.IssuerName)
165 if (serial.Length != x509.SerialNumber.Length)
167 // MS shows the serial number inversed (so is Mono.Security.X509.X509Certificate)
168 int n = serial.Length;
169 for (int i=0; i < serial.Length; i++) {
170 if (serial [i] != x509.SerialNumber [--n])
177 //private bool VerifySignature (ASN1 cs, byte[] calculatedMessageDigest, string hashName)
178 private bool VerifySignature (PKCS7.SignedData sd, byte[] calculatedMessageDigest, string hashName)
180 string contentType = null;
181 ASN1 messageDigest = null;
182 string spcStatementType = null;
183 string spcSpOpusInfo = null;
185 for (int i=0; i < sd.SignerInfo.AuthenticatedAttributes.Count; i++) {
186 ASN1 attr = (ASN1) sd.SignerInfo.AuthenticatedAttributes [i];
187 string oid = ASN1Convert.ToOID (attr[0]);
189 case "1.2.840.113549.1.9.3":
191 contentType = ASN1Convert.ToOID (attr[1][0]);
193 case "1.2.840.113549.1.9.4":
195 messageDigest = attr[1][0];
197 case "1.3.6.1.4.1.311.2.1.11":
198 // spcStatementType (Microsoft code signing)
200 // - individualCodeSigning (1 3 6 1 4 1 311 2 1 21)
201 // - commercialCodeSigning (1 3 6 1 4 1 311 2 1 22)
202 spcStatementType = ASN1Convert.ToOID (attr[1][0][0]);
204 case "1.3.6.1.4.1.311.2.1.12":
205 // spcSpOpusInfo (Microsoft code signing)
207 spcSpOpusInfo = System.Text.Encoding.UTF8.GetString (attr[1][0][1][0].Value);
210 spcSpOpusInfo = null;
217 if (contentType != spcIndirectDataContext)
220 // verify message digest
221 if (messageDigest == null)
223 if (!messageDigest.CompareValue (calculatedMessageDigest))
227 string hashOID = CryptoConfig.MapNameToOID (hashName);
229 // change to SET OF (not [0]) as per PKCS #7 1.5
230 ASN1 aa = new ASN1 (0x31);
231 foreach (ASN1 a in sd.SignerInfo.AuthenticatedAttributes)
233 HashAlgorithm ha = HashAlgorithm.Create (hashName);
234 byte[] p7hash = ha.ComputeHash (aa.GetBytes ());
236 byte[] signature = sd.SignerInfo.Signature;
237 // we need to find the specified certificate
238 string issuer = sd.SignerInfo.IssuerName;
239 byte[] serial = sd.SignerInfo.SerialNumber;
240 foreach (X509Certificate x509 in coll) {
241 if (CompareIssuerSerial (issuer, serial, x509)) {
242 // don't verify is key size don't match
243 if (x509.PublicKey.Length > (signature.Length >> 3)) {
244 RSACryptoServiceProvider rsa = (RSACryptoServiceProvider) x509.RSA;
245 if (rsa.VerifyHash (p7hash, hashOID, signature)) {
246 signerChain.LoadCertificates (coll);
247 if (signerChain.GetChain (x509) != null)
248 signingCertificate = x509;
256 for (int i=0; i < sd.SignerInfo.UnauthenticatedAttributes.Count; i++) {
257 ASN1 attr = (ASN1) sd.SignerInfo.UnauthenticatedAttributes [i];
258 string oid = ASN1Convert.ToOID (attr [0]);
260 case PKCS7.countersignature:
263 // countersignature (1 2 840 113549 1 9 6)
265 PKCS7.SignerInfo cs = new PKCS7.SignerInfo (attr [1]);
266 return VerifyCounterSignature (cs, signature, hashName);
268 // we don't support other unauthenticated attributes
276 //private bool VerifyCounterSignature (ASN1 cs, byte[] signature, string hashName)
277 private bool VerifyCounterSignature (PKCS7.SignerInfo cs, byte[] signature, string hashName)
286 string contentType = null;
287 ASN1 messageDigest = null;
288 for (int i=0; i < cs.AuthenticatedAttributes.Count; i++) {
291 ASN1 attr = (ASN1) cs.AuthenticatedAttributes [i];
292 string oid = ASN1Convert.ToOID (attr[0]);
294 case "1.2.840.113549.1.9.3":
296 contentType = ASN1Convert.ToOID (attr[1][0]);
298 case "1.2.840.113549.1.9.4":
300 messageDigest = attr[1][0];
302 case "1.2.840.113549.1.9.5":
305 // signingTime (1 2 840 113549 1 9 5)
307 // UTCTime '030124013651Z'
310 timestamp = ASN1Convert.ToDateTime (attr[1][0]);
317 if (contentType != PKCS7.data)
320 // verify message digest
321 if (messageDigest == null)
323 // TODO: must be read from the ASN.1 structure
324 switch (messageDigest.Length) {
332 HashAlgorithm ha = HashAlgorithm.Create (hashName);
333 if (!messageDigest.CompareValue (ha.ComputeHash (signature)))
337 byte[] counterSignature = cs.Signature;
338 string hashOID = CryptoConfig.MapNameToOID (hashName);
340 // change to SET OF (not [0]) as per PKCS #7 1.5
341 ASN1 aa = new ASN1 (0x31);
342 foreach (ASN1 a in cs.AuthenticatedAttributes)
344 byte[] p7hash = ha.ComputeHash (aa.GetBytes ());
346 // we need to try all certificates
347 string issuer = cs.IssuerName;
348 byte[] serial = cs.SerialNumber;
349 foreach (X509Certificate x509 in coll) {
350 if (CompareIssuerSerial (issuer, serial, x509)) {
351 // don't verify is key size don't match
352 if (x509.PublicKey.Length > (counterSignature.Length >> 3)) {
353 RSACryptoServiceProvider rsa = (RSACryptoServiceProvider) x509.RSA;
354 if (rsa.VerifyHash (p7hash, hashOID, counterSignature)) {
355 timestampChain.LoadCertificates (coll);
356 return (timestampChain.GetChain (x509) != null);
361 // no certificate can verify this signature!