Merge pull request #2274 from esdrubal/udpclientreceive
[mono.git] / mcs / class / Mono.Security / Mono.Security.Authenticode / AuthenticodeFormatter.cs
old mode 100755 (executable)
new mode 100644 (file)
index 1c1540b..f91a3a9
@@ -5,7 +5,7 @@
 //     Sebastien Pouliot <sebastien@ximian.com>
 //
 // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
-// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
+// Copyright (C) 2004, 2006-2007 Novell, Inc (http://www.novell.com)
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
@@ -52,7 +52,6 @@ namespace Mono.Security.Authenticode {
                private PKCS7.SignedData pkcs7;
                private string description;
                private Uri url;
-               private byte [] entry;
 
                public AuthenticodeFormatter () : base () 
                {
@@ -78,7 +77,7 @@ namespace Mono.Security.Authenticode {
                public string Hash {
                        get { 
                                if (hash == null)
-                                       hash = "MD5";
+                                       hash = "SHA1";
                                return hash; 
                        }
                        set {
@@ -149,23 +148,23 @@ namespace Mono.Security.Authenticode {
                }
 
                // pkcs 1
-               private const string rsaEncryption = "1.2.840.113549.1.1.1";
+//             private const string rsaEncryption = "1.2.840.113549.1.1.1";
                // pkcs 7
-               private const string data = "1.2.840.113549.1.7.1";
+//             private const string data = "1.2.840.113549.1.7.1";
                private const string signedData = "1.2.840.113549.1.7.2";
                // pkcs 9
-               private const string contentType = "1.2.840.113549.1.9.3";
-               private const string messageDigest  = "1.2.840.113549.1.9.4";
+//             private const string contentType = "1.2.840.113549.1.9.3";
+//             private const string messageDigest  = "1.2.840.113549.1.9.4";
                private const string countersignature = "1.2.840.113549.1.9.6";
                // microsoft spc (software publisher certificate)
                private const string spcStatementType = "1.3.6.1.4.1.311.2.1.11";
                private const string spcSpOpusInfo = "1.3.6.1.4.1.311.2.1.12";
                private const string spcPelmageData = "1.3.6.1.4.1.311.2.1.15";
-               private const string individualCodeSigning = "1.3.6.1.4.1.311.2.1.21";
+//             private const string individualCodeSigning = "1.3.6.1.4.1.311.2.1.21";
                private const string commercialCodeSigning = "1.3.6.1.4.1.311.2.1.22";
                private const string timestampCountersignature = "1.3.6.1.4.1.311.3.2.1";
 
-               private static byte[] version = { 0x01 };
+               //private static byte[] version = { 0x01 };
                private static byte[] obsolete = { 0x03, 0x01, 0x00, 0xA0, 0x20, 0xA2, 0x1E, 0x80, 0x1C, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x4F, 0x00, 0x62, 0x00, 0x73, 0x00, 0x6F, 0x00, 0x6C, 0x00, 0x65, 0x00, 0x74, 0x00, 0x65, 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x3E };
 
                private byte[] Header (byte[] fileHash, string hashAlgorithm) 
@@ -189,15 +188,13 @@ namespace Mono.Security.Authenticode {
 
                        ASN1 opus = null;
                        if (url == null)
-                               Attribute (spcSpOpusInfo, Opus (description, null));
+                               opus = Attribute (spcSpOpusInfo, Opus (description, null));
                        else
-                               Attribute (spcSpOpusInfo, Opus (description, url.ToString ()));
+                               opus = Attribute (spcSpOpusInfo, Opus (description, url.ToString ()));
                        pkcs7.SignerInfo.AuthenticatedAttributes.Add (opus);
-
-                       pkcs7.SignerInfo.AuthenticatedAttributes.Add (Attribute (contentType, ASN1Convert.FromOid (spcIndirectDataContext)));
-                       pkcs7.SignerInfo.AuthenticatedAttributes.Add (Attribute (spcStatementType, new ASN1 (0x30, ASN1Convert.FromOid (commercialCodeSigning).GetBytes ())));
-
-                       ASN1 temp = pkcs7.ASN1; // sign
+// When using the MS Root Agency (test) we can't include this attribute in the signature or it won't validate!
+//                     pkcs7.SignerInfo.AuthenticatedAttributes.Add (Attribute (spcStatementType, new ASN1 (0x30, ASN1Convert.FromOid (commercialCodeSigning).GetBytes ())));
+                       pkcs7.GetASN1 (); // sign
                        return pkcs7.SignerInfo.Signature;
                }
 
@@ -222,111 +219,158 @@ namespace Mono.Security.Authenticode {
                        pkcs7.SignerInfo.UnauthenticatedAttributes.Add (Attribute (countersignature, ts[1][0][4][0]));
                }
 
-               public bool Sign (string fileName) 
+               private byte[] Timestamp (byte[] signature)
                {
-                       string hashAlgorithm = "MD5";
-                       FileStream fs = new FileStream (fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
-                       byte[] file = new byte [fs.Length];
-                       fs.Read (file, 0, file.Length);
-                       fs.Close ();
-
-                       // MZ - DOS header
-                       if (BitConverter.ToUInt16 (file, 0) != 0x5A4D)
-                               return false;
-
-                       // find offset of PE header
-                       int peOffset = BitConverter.ToInt32 (file, 60);
-                       if (peOffset > file.Length)
-                               return false;
-
-                       // PE - NT header
-                       if (BitConverter.ToUInt16 (file, peOffset) != 0x4550)
-                               return false;
-
-                       // IMAGE_DIRECTORY_ENTRY_SECURITY
-                       int dirSecurityOffset = BitConverter.ToInt32 (file, peOffset + 152);
-                       int dirSecuritySize = BitConverter.ToInt32 (file, peOffset + 156);
-
-                       if (dirSecuritySize > 8) {
-                               entry = new byte [dirSecuritySize - 8];
-                               Buffer.BlockCopy (file, dirSecurityOffset + 8, entry, 0, entry.Length);
-                       }
-                       else
-                               entry = null;
-
-                       HashAlgorithm hash = HashAlgorithm.Create (hashAlgorithm);
-                       // 0 to 215 (216) then skip 4 (checksum)
-                       int pe = peOffset + 88;
-                       hash.TransformBlock (file, 0, pe, file, 0);
-                       pe += 4;
-                       // 220 to 279 (60) then skip 8 (IMAGE_DIRECTORY_ENTRY_SECURITY)
-                       hash.TransformBlock (file, pe, 60, file, pe);
-                       pe += 68;
-                       // 288 to end of file
-                       int n = file.Length - pe;
-                       // minus any authenticode signature (with 8 bytes header)
-                       if (dirSecurityOffset != 0)
-                               n -= (dirSecuritySize);
-                       hash.TransformFinalBlock (file, pe, n);
-
-                       //
-                       byte[] signature = Header (hash.Hash, hashAlgorithm);
-                       if (timestamp != null) {
-                               ASN1 tsreq = TimestampRequest (signature);
-                               WebClient wc = new WebClient ();
-                               wc.Headers.Add ("Content-Type", "application/octet-stream");
-                               wc.Headers.Add ("Accept", "application/octet-stream");
-                               byte[] tsdata = Encoding.ASCII.GetBytes (Convert.ToBase64String (tsreq.GetBytes ()));
-                               byte[] tsres = wc.UploadData (timestamp.ToString (), tsdata);
-                               ProcessTimestamp (tsres);
+                       ASN1 tsreq = TimestampRequest (signature);
+                       WebClient wc = new WebClient ();
+                       wc.Headers.Add ("Content-Type", "application/octet-stream");
+                       wc.Headers.Add ("Accept", "application/octet-stream");
+                       byte[] tsdata = Encoding.ASCII.GetBytes (Convert.ToBase64String (tsreq.GetBytes ()));
+                       return wc.UploadData (timestamp.ToString (), tsdata);
+               }
+
+               private bool Save (string fileName, byte[] asn)
+               {
+#if DEBUG
+                       using (FileStream fs = File.Open (fileName + ".sig", FileMode.Create, FileAccess.Write)) {
+                               fs.Write (asn, 0, asn.Length);
+                               fs.Close ();
                        }
-                       PKCS7.ContentInfo sign = new PKCS7.ContentInfo (signedData);
-                       sign.Content.Add (pkcs7.ASN1);
-                       authenticode = sign.ASN1;
+#endif
+                       // someday I may be sure enough to move this into DEBUG ;-)
+                       File.Copy (fileName, fileName + ".bak", true);
 
-                       // debug
-                       fs = File.Open (fileName + ".sig", FileMode.Create, FileAccess.Write);
-                       byte[] asn = authenticode.GetBytes ();
-                       fs.Write (asn, 0, asn.Length);
-                       fs.Close ();
+                       using (FileStream fs = File.Open (fileName, FileMode.Open, FileAccess.ReadWrite)) {
+                               int filesize;
+                               if (SecurityOffset > 0) {
+                                       // file was already signed, we'll reuse the position for the updated signature
+                                       filesize = SecurityOffset;
+                               } else if (CoffSymbolTableOffset > 0) {
+                                       // strip (deprecated) COFF symbol table
+                                       fs.Seek (PEOffset + 12, SeekOrigin.Begin);
+                                       for (int i = 0; i < 8; i++)
+                                               fs.WriteByte (0);
+                                       // we'll put the Authenticode signature at this same place (just after the last section)
+                                       filesize = CoffSymbolTableOffset;
+                               } else {
+                                       // file was never signed, nor does it contains (deprecated) COFF symbols
+                                       filesize = (int)fs.Length;
+                               }
+                               // must be a multiple of 8 bytes
+                               int addsize = (filesize & 7);
+                               if (addsize > 0)
+                                       addsize = 8 - addsize;
+
+                               // IMAGE_DIRECTORY_ENTRY_SECURITY (offset, size)
+                               byte[] data = BitConverterLE.GetBytes (filesize + addsize);
+                               fs.Seek (PEOffset + 152, SeekOrigin.Begin);
+                               fs.Write (data, 0, 4);
+                               int size = asn.Length + 8;
+                               int addsize_signature = (size & 7);
+                               if (addsize_signature > 0)
+                                       addsize_signature = 8 - addsize_signature;
+                               data = BitConverterLE.GetBytes (size + addsize_signature);
+                               fs.Seek (PEOffset + 156, SeekOrigin.Begin);
+                               fs.Write (data, 0, 4);
+                               fs.Seek (filesize, SeekOrigin.Begin);
+                               // align certificate entry to a multiple of 8 bytes
+                               if (addsize > 0) {
+                                       byte[] fillup = new byte[addsize];
+                                       fs.Write (fillup, 0, fillup.Length);
+                               }
+                               fs.Write (data, 0, data.Length);                // length (again)
+                               data = BitConverterLE.GetBytes (0x00020200);    // magic
+                               fs.Write (data, 0, data.Length);
+                               fs.Write (asn, 0, asn.Length);
+                               if (addsize_signature > 0) {
+                                       byte[] fillup = new byte[addsize_signature];
+                                       fs.Write (fillup, 0, fillup.Length);
+                               }
+                               fs.Close ();
+                       }
+                       return true;
+               }
 
-                       File.Copy (fileName, fileName + ".bak", true);
+               public bool Sign (string fileName) 
+               {
+                       try {
+                               Open (fileName);
+
+                               HashAlgorithm hash = HashAlgorithm.Create (Hash);
+                               // 0 to 215 (216) then skip 4 (checksum)
+
+                               byte[] digest = GetHash (hash);
+                               byte[] signature = Header (digest, Hash);
+                               if (timestamp != null) {
+                                       byte[] ts = Timestamp (signature);
+                                       // add timestamp information inside the current pkcs7 SignedData instance
+                                       // (this is possible because the data isn't yet signed)
+                                       ProcessTimestamp (ts);
+                               }
 
-                       fs = File.Open (fileName, FileMode.Create, FileAccess.Write);
-                       // IMAGE_DIRECTORY_ENTRY_SECURITY (offset, size)
-                       byte[] data = BitConverter.GetBytes (file.Length);
-                       file [peOffset + 152] = data [0];
-                       file [peOffset + 153] = data [1];
-                       file [peOffset + 154] = data [2];
-                       file [peOffset + 155] = data [3];
-                       int size = asn.Length + 8;
-                       // must be a multiple of 8 bytes
-                       int addsize = (size % 8);
-                       if (addsize > 0)
-                               addsize = 8 - addsize;
-                       size += addsize;
-                       data = BitConverter.GetBytes (size);            // header
-                       file [peOffset + 156] = data [0];
-                       file [peOffset + 157] = data [1];
-                       file [peOffset + 158] = data [2];
-                       file [peOffset + 159] = data [3];
-                       fs.Write (file, 0, file.Length);
-                       fs.Write (data, 0, data.Length);                // length (again)
-                       data = BitConverter.GetBytes (0x00020200);      // magic
-                       fs.Write (data, 0, data.Length);
-                       fs.Write (asn, 0, asn.Length);
-                       // fill up
-                       byte[] fillup = new byte [addsize];
-                       fs.Write (fillup, 0, fillup.Length);
-                       fs.Close ();
+                               PKCS7.ContentInfo sign = new PKCS7.ContentInfo (signedData);
+                               sign.Content.Add (pkcs7.ASN1);
+                               authenticode = sign.ASN1;
+                               Close ();
 
-                       return true;
+                               return Save (fileName, authenticode.GetBytes ());
+                       }
+                       catch (Exception e) {
+                               Console.WriteLine (e);
+                       }
+                       return false;
                }
 
                // in case we just want to timestamp the file
                public bool Timestamp (string fileName) 
                {
-                       return true;
+                       try {
+                               AuthenticodeDeformatter def = new AuthenticodeDeformatter (fileName);
+                               byte[] signature = def.Signature;
+                               if (signature != null) {
+                                       Open (fileName);
+                                       PKCS7.ContentInfo ci = new PKCS7.ContentInfo (signature);
+                                       pkcs7 = new PKCS7.SignedData (ci.Content);
+
+                                       byte[] response = Timestamp (pkcs7.SignerInfo.Signature);
+                                       ASN1 ts = new ASN1 (Convert.FromBase64String (Encoding.ASCII.GetString (response)));
+                                       // insert new certificates and countersignature into the original signature
+                                       ASN1 asn = new ASN1 (signature);
+                                       ASN1 content = asn.Element (1, 0xA0);
+                                       if (content == null)
+                                               return false;
+
+                                       ASN1 signedData = content.Element (0, 0x30);
+                                       if (signedData == null)
+                                               return false;
+
+                                       // add the supplied certificates inside our signature
+                                       ASN1 certificates = signedData.Element (3, 0xA0);
+                                       if (certificates == null) {
+                                               certificates = new ASN1 (0xA0);
+                                               signedData.Add (certificates);
+                                       }
+                                       for (int i = 0; i < ts[1][0][3].Count; i++) {
+                                               certificates.Add (ts[1][0][3][i]);
+                                       }
+
+                                       // add an unauthentified attribute to our signature
+                                       ASN1 signerInfoSet = signedData[signedData.Count - 1];
+                                       ASN1 signerInfo = signerInfoSet[0];
+                                       ASN1 unauthenticated = signerInfo[signerInfo.Count - 1];
+                                       if (unauthenticated.Tag != 0xA1) {
+                                               unauthenticated = new ASN1 (0xA1);
+                                               signerInfo.Add (unauthenticated);
+                                       }
+                                       unauthenticated.Add (Attribute (countersignature, ts[1][0][4][0]));
+
+                                       return Save (fileName, asn.GetBytes ());
+                               }
+                       }
+                       catch (Exception e) {
+                               Console.WriteLine (e);
+                       }
+                       return false;
                }
        }
 }