2 // AuthenticodeFormatter.cs: Authenticode signature generator
5 // Sebastien Pouliot (spouliot@motus.com)
7 // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
11 using System.Collections;
13 using System.Security.Cryptography;
14 //using System.Security.Cryptography.X509Certificates;
19 using Mono.Security.X509;
21 namespace Mono.Security.Authenticode {
23 public class AuthenticodeFormatter : AuthenticodeBase {
25 private Authority authority;
26 private X509CertificateCollection certs;
27 private ArrayList crls;
30 private string timestamp;
31 private ASN1 authenticode;
32 private PKCS7.SignedData pkcs7;
33 private string description;
36 public AuthenticodeFormatter () : base ()
38 certs = new X509CertificateCollection ();
39 crls = new ArrayList ();
40 authority = Authority.Maximum;
41 pkcs7 = new PKCS7.SignedData ();
44 public Authority Authority {
45 get { return authority; }
46 set { authority = value; }
49 public X509CertificateCollection Certificates {
53 public ArrayList CRL {
64 string h = value.ToUpper ();
65 if ((h == "MD5") || (hash == "SHA1"))
68 throw new ArgumentException ("Invalid Authenticode hash algorithm");
77 public string TimestampURL {
78 get { return timestamp; }
79 set { timestamp = value; } // URL
82 public string Description {
83 get { return description; }
84 set { description = value; }
89 set { url = value; } // URL
92 private ASN1 AlgorithmIdentifier (string oid)
94 ASN1 ai = new ASN1 (0x30);
95 ai.Add (ASN1Convert.FromOID (oid));
96 ai.Add (new ASN1 (0x05)); // NULL
100 private ASN1 Attribute (string oid, ASN1 value)
102 ASN1 attr = new ASN1 (0x30);
103 attr.Add (ASN1Convert.FromOID (oid));
104 ASN1 aset = attr.Add (new ASN1 (0x31));
109 private ASN1 Opus (string description, string url)
111 ASN1 opus = new ASN1 (0x30);
112 if (description != null) {
113 ASN1 part1 = opus.Add (new ASN1 (0xA0));
114 part1.Add (new ASN1 (0x80, Encoding.BigEndianUnicode.GetBytes (description)));
117 ASN1 part2 = opus.Add (new ASN1 (0xA1));
118 part2.Add (new ASN1 (0x80, Encoding.ASCII.GetBytes (url)));
124 private const string rsaEncryption = "1.2.840.113549.1.1.1";
126 private const string data = "1.2.840.113549.1.7.1";
127 private const string signedData = "1.2.840.113549.1.7.2";
129 private const string contentType = "1.2.840.113549.1.9.3";
130 private const string messageDigest = "1.2.840.113549.1.9.4";
131 private const string countersignature = "1.2.840.113549.1.9.6";
132 // microsoft spc (software publisher certificate)
133 private const string spcStatementType = "1.3.6.1.4.1.311.2.1.11";
134 private const string spcSpOpusInfo = "1.3.6.1.4.1.311.2.1.12";
135 private const string spcPelmageData = "1.3.6.1.4.1.311.2.1.15";
136 private const string individualCodeSigning = "1.3.6.1.4.1.311.2.1.21";
137 private const string commercialCodeSigning = "1.3.6.1.4.1.311.2.1.22";
138 private const string timestampCountersignature = "1.3.6.1.4.1.311.3.2.1";
140 private static byte[] version = { 0x01 };
141 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 };
143 private byte[] Header (byte[] fileHash, string hashAlgorithm)
145 string hashOid = CryptoConfig.MapNameToOID (hashAlgorithm);
146 ASN1 content = new ASN1 (0x30);
147 ASN1 c1 = content.Add (new ASN1 (0x30));
148 c1.Add (ASN1Convert.FromOID (spcPelmageData));
149 c1.Add (new ASN1 (0x30, obsolete));
150 ASN1 c2 = content.Add (new ASN1 (0x30));
151 c2.Add (AlgorithmIdentifier (hashOid));
152 c2.Add (new ASN1 (0x04, fileHash));
154 pkcs7.HashName = hashAlgorithm;
155 pkcs7.Certificates.AddRange (certs);
156 pkcs7.ContentInfo.ContentType = spcIndirectDataContext;
157 pkcs7.ContentInfo.Content.Add (content);
159 pkcs7.SignerInfo.Certificate = certs [0];
160 pkcs7.SignerInfo.Key = rsa;
161 pkcs7.SignerInfo.AuthenticatedAttributes.Add (Attribute (spcSpOpusInfo, Opus (description, url)));
162 pkcs7.SignerInfo.AuthenticatedAttributes.Add (Attribute (contentType, ASN1Convert.FromOID (spcIndirectDataContext)));
163 pkcs7.SignerInfo.AuthenticatedAttributes.Add (Attribute (spcStatementType, new ASN1 (0x30, ASN1Convert.FromOID (commercialCodeSigning).GetBytes ())));
165 ASN1 temp = pkcs7.ASN1; // sign
166 return pkcs7.SignerInfo.Signature;
169 public ASN1 TimestampRequest (byte[] signature)
171 PKCS7.ContentInfo ci = new PKCS7.ContentInfo (PKCS7.data);
172 ci.Content.Add (new ASN1 (0x04, signature));
173 return PKCS7.AlgorithmIdentifier (timestampCountersignature, ci.ASN1);
176 public void ProcessTimestamp (byte[] tsres)
178 ASN1 ts = new ASN1 (Convert.FromBase64String (Encoding.ASCII.GetString (tsres)));
179 // first validate the received message
182 // add the supplied certificates inside our signature
183 for (int i=0; i < ts[1][0][3].Count; i++)
184 pkcs7.Certificates.Add (new X509Certificate (ts[1][0][3][i].GetBytes ()));
186 // add an unauthentified attribute to our signature
187 pkcs7.SignerInfo.UnauthenticatedAttributes.Add (Attribute (countersignature, ts[1][0][4][0]));
190 public bool Sign (string fileName)
192 string hashAlgorithm = "MD5";
193 FileStream fs = new FileStream (fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
194 byte[] file = new byte [fs.Length];
195 fs.Read (file, 0, file.Length);
199 if (BitConverter.ToUInt16 (file, 0) != 0x5A4D)
202 // find offset of PE header
203 int peOffset = BitConverter.ToInt32 (file, 60);
204 if (peOffset > file.Length)
208 if (BitConverter.ToUInt16 (file, peOffset) != 0x4550)
211 // IMAGE_DIRECTORY_ENTRY_SECURITY
212 int dirSecurityOffset = BitConverter.ToInt32 (file, peOffset + 152);
213 int dirSecuritySize = BitConverter.ToInt32 (file, peOffset + 156);
215 if (dirSecuritySize > 8) {
216 rawData = new byte [dirSecuritySize - 8];
217 Array.Copy (file, dirSecurityOffset + 8, rawData, 0, rawData.Length);
222 HashAlgorithm hash = HashAlgorithm.Create (hashAlgorithm);
223 // 0 to 215 (216) then skip 4 (checksum)
224 int pe = peOffset + 88;
225 hash.TransformBlock (file, 0, pe, file, 0);
227 // 220 to 279 (60) then skip 8 (IMAGE_DIRECTORY_ENTRY_SECURITY)
228 hash.TransformBlock (file, pe, 60, file, pe);
230 // 288 to end of file
231 int n = file.Length - pe;
232 // minus any authenticode signature (with 8 bytes header)
233 if (dirSecurityOffset != 0)
234 n -= (dirSecuritySize);
235 hash.TransformFinalBlock (file, pe, n);
238 byte[] signature = Header (hash.Hash, hashAlgorithm);
239 if (timestamp != null) {
240 ASN1 tsreq = TimestampRequest (signature);
241 WebClient wc = new WebClient ();
242 wc.Headers.Add ("Content-Type", "application/octet-stream");
243 wc.Headers.Add ("Accept", "application/octet-stream");
244 byte[] tsdata = Encoding.ASCII.GetBytes (Convert.ToBase64String (tsreq.GetBytes ()));
245 byte[] tsres = wc.UploadData (timestamp, tsdata);
246 ProcessTimestamp (tsres);
248 PKCS7.ContentInfo sign = new PKCS7.ContentInfo (signedData);
249 sign.Content.Add (pkcs7.ASN1);
250 authenticode = sign.ASN1;
253 fs = File.Open (fileName + ".sig", FileMode.Create, FileAccess.Write);
254 byte[] asn = authenticode.GetBytes ();
255 fs.Write (asn, 0, asn.Length);
258 File.Copy (fileName, fileName + ".bak", true);
260 fs = File.Open (fileName, FileMode.Create, FileAccess.Write);
261 // IMAGE_DIRECTORY_ENTRY_SECURITY (offset, size)
262 byte[] data = BitConverter.GetBytes (file.Length);
263 file [peOffset + 152] = data [0];
264 file [peOffset + 153] = data [1];
265 file [peOffset + 154] = data [2];
266 file [peOffset + 155] = data [3];
267 int size = asn.Length + 8;
268 // must be a multiple of 8 bytes
269 int addsize = (size % 8);
271 addsize = 8 - addsize;
273 data = BitConverter.GetBytes (size); // header
274 file [peOffset + 156] = data [0];
275 file [peOffset + 157] = data [1];
276 file [peOffset + 158] = data [2];
277 file [peOffset + 159] = data [3];
278 fs.Write (file, 0, file.Length);
279 fs.Write (data, 0, data.Length); // length (again)
280 data = BitConverter.GetBytes (0x00020200); // magic
281 fs.Write (data, 0, data.Length);
282 fs.Write (asn, 0, asn.Length);
284 byte[] fillup = new byte [addsize];
285 fs.Write (fillup, 0, fillup.Length);
291 // in case we just want to timestamp the file
292 public bool Timestamp (string fileName)