// // AuthenticodeFormatter.cs: Authenticode signature generator // // Author: // Sebastien Pouliot // // (C) 2003 Motus Technologies Inc. (http://www.motus.com) // Copyright (C) 2004 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 // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections; using System.Globalization; using System.IO; using System.Security.Cryptography; using System.Text; using System.Net; using Mono.Security; using Mono.Security.X509; namespace Mono.Security.Authenticode { public class AuthenticodeFormatter : AuthenticodeBase { private Authority authority; private X509CertificateCollection certs; private ArrayList crls; private string hash; private RSA rsa; private Uri timestamp; private ASN1 authenticode; private PKCS7.SignedData pkcs7; private string description; private Uri url; private byte [] entry; public AuthenticodeFormatter () : base () { certs = new X509CertificateCollection (); crls = new ArrayList (); authority = Authority.Maximum; pkcs7 = new PKCS7.SignedData (); } public Authority Authority { get { return authority; } set { authority = value; } } public X509CertificateCollection Certificates { get { return certs; } } public ArrayList Crl { get { return crls; } } public string Hash { get { if (hash == null) hash = "MD5"; return hash; } set { if (value == null) throw new ArgumentNullException ("Hash"); string h = value.ToUpper (CultureInfo.InvariantCulture); switch (h) { case "MD5": case "SHA1": hash = h; break; default: throw new ArgumentException ("Invalid Authenticode hash algorithm"); } } } public RSA RSA { get { return rsa; } set { rsa = value; } } public Uri TimestampUrl { get { return timestamp; } set { timestamp = value; } } public string Description { get { return description; } set { description = value; } } public Uri Url { get { return url; } set { url = value; } } private ASN1 AlgorithmIdentifier (string oid) { ASN1 ai = new ASN1 (0x30); ai.Add (ASN1Convert.FromOid (oid)); ai.Add (new ASN1 (0x05)); // NULL return ai; } private ASN1 Attribute (string oid, ASN1 value) { ASN1 attr = new ASN1 (0x30); attr.Add (ASN1Convert.FromOid (oid)); ASN1 aset = attr.Add (new ASN1 (0x31)); aset.Add (value); return attr; } private ASN1 Opus (string description, string url) { ASN1 opus = new ASN1 (0x30); if (description != null) { ASN1 part1 = opus.Add (new ASN1 (0xA0)); part1.Add (new ASN1 (0x80, Encoding.BigEndianUnicode.GetBytes (description))); } if (url != null) { ASN1 part2 = opus.Add (new ASN1 (0xA1)); part2.Add (new ASN1 (0x80, Encoding.ASCII.GetBytes (url))); } return opus; } // pkcs 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 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 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 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[] 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) { string hashOid = CryptoConfig.MapNameToOID (hashAlgorithm); ASN1 content = new ASN1 (0x30); ASN1 c1 = content.Add (new ASN1 (0x30)); c1.Add (ASN1Convert.FromOid (spcPelmageData)); c1.Add (new ASN1 (0x30, obsolete)); ASN1 c2 = content.Add (new ASN1 (0x30)); c2.Add (AlgorithmIdentifier (hashOid)); c2.Add (new ASN1 (0x04, fileHash)); pkcs7.HashName = hashAlgorithm; pkcs7.Certificates.AddRange (certs); pkcs7.ContentInfo.ContentType = spcIndirectDataContext; pkcs7.ContentInfo.Content.Add (content); pkcs7.SignerInfo.Certificate = certs [0]; pkcs7.SignerInfo.Key = rsa; ASN1 opus = null; if (url == null) opus = Attribute (spcSpOpusInfo, Opus (description, null)); else opus = Attribute (spcSpOpusInfo, Opus (description, url.ToString ())); pkcs7.SignerInfo.AuthenticatedAttributes.Add (opus); pkcs7.SignerInfo.AuthenticatedAttributes.Add (Attribute (spcStatementType, new ASN1 (0x30, ASN1Convert.FromOid (commercialCodeSigning).GetBytes ()))); pkcs7.GetASN1 (); // sign return pkcs7.SignerInfo.Signature; } public ASN1 TimestampRequest (byte[] signature) { PKCS7.ContentInfo ci = new PKCS7.ContentInfo (PKCS7.Oid.data); ci.Content.Add (new ASN1 (0x04, signature)); return PKCS7.AlgorithmIdentifier (timestampCountersignature, ci.ASN1); } public void ProcessTimestamp (byte[] response) { ASN1 ts = new ASN1 (Convert.FromBase64String (Encoding.ASCII.GetString (response))); // first validate the received message // TODO // add the supplied certificates inside our signature for (int i=0; i < ts[1][0][3].Count; i++) pkcs7.Certificates.Add (new X509Certificate (ts[1][0][3][i].GetBytes ())); // add an unauthentified attribute to our signature pkcs7.SignerInfo.UnauthenticatedAttributes.Add (Attribute (countersignature, ts[1][0][4][0])); } private byte[] Timestamp (byte[] signature) { 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 byte[] file; private int pe_offset = -1; private int sec_offset = -1; private int sec_size = -1; private int PEOffset { get { if (file == null) return -1; if (pe_offset == -1) { int peOffset = BitConverterLE.ToInt32 (file, 60); if (peOffset < file.Length) pe_offset = peOffset; } return pe_offset; } } private int SecurityOffset { get { if (file == null) return -1; if (sec_offset == -1) { sec_offset = BitConverterLE.ToInt32 (file, PEOffset + 152); } return sec_offset; } } private int SecuritySize { get { if (file == null) return -1; if (sec_size == -1) { sec_size = BitConverterLE.ToInt32 (file, PEOffset + 156); } return sec_size; } } private bool Open (string fileName) { using (FileStream fs = new FileStream (fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) { file = new byte[fs.Length]; fs.Read (file, 0, file.Length); fs.Close (); } // MZ - DOS header if (BitConverterLE.ToUInt16 (file, 0) != 0x5A4D) return false; // PE Header if (PEOffset == -1) return false; // PE - NT header if (BitConverterLE.ToUInt16 (file, PEOffset) != 0x4550) return false; // IMAGE_DIRECTORY_ENTRY_SECURITY if (SecuritySize > 8) { entry = new byte[SecuritySize - 8]; Buffer.BlockCopy (file, SecurityOffset + 8, entry, 0, entry.Length); } else entry = null; return true; } 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 (); } #endif // someday I may be sure enough to move this into DEBUG ;-) File.Copy (fileName, fileName + ".bak", true); using (FileStream fs = File.Open (fileName, FileMode.Create, FileAccess.Write)) { int filesize = (SecurityOffset == 0) ? file.Length : SecurityOffset; // IMAGE_DIRECTORY_ENTRY_SECURITY (offset, size) byte[] data = BitConverterLE.GetBytes (filesize); 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 = BitConverterLE.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, filesize); 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); // fill up byte[] fillup = new byte[addsize]; fs.Write (fillup, 0, fillup.Length); fs.Close (); } return true; } public bool Sign (string fileName) { try { if (!Open (fileName)) return false; HashAlgorithm hash = HashAlgorithm.Create (Hash); // 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 (SecurityOffset != 0) n -= (SecuritySize); hash.TransformFinalBlock (file, pe, n); byte[] signature = Header (hash.Hash, 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); } PKCS7.ContentInfo sign = new PKCS7.ContentInfo (signedData); sign.Content.Add (pkcs7.ASN1); authenticode = sign.ASN1; 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) { 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; } } }