2 // AuthenticodeFormatter.cs: Authenticode signature generator
5 // Sebastien Pouliot <sebastien@ximian.com>
7 // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
8 // Copyright (C) 2004, 2006-2007 Novell, Inc (http://www.novell.com)
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System.Collections;
32 using System.Globalization;
34 using System.Security.Cryptography;
39 using Mono.Security.X509;
41 namespace Mono.Security.Authenticode {
43 public class AuthenticodeFormatter : AuthenticodeBase {
45 private Authority authority;
46 private X509CertificateCollection certs;
47 private ArrayList crls;
50 private Uri timestamp;
51 private ASN1 authenticode;
52 private PKCS7.SignedData pkcs7;
53 private string description;
56 public AuthenticodeFormatter () : base ()
58 certs = new X509CertificateCollection ();
59 crls = new ArrayList ();
60 authority = Authority.Maximum;
61 pkcs7 = new PKCS7.SignedData ();
64 public Authority Authority {
65 get { return authority; }
66 set { authority = value; }
69 public X509CertificateCollection Certificates {
73 public ArrayList Crl {
85 throw new ArgumentNullException ("Hash");
87 string h = value.ToUpper (CultureInfo.InvariantCulture);
94 throw new ArgumentException ("Invalid Authenticode hash algorithm");
104 public Uri TimestampUrl {
105 get { return timestamp; }
106 set { timestamp = value; }
109 public string Description {
110 get { return description; }
111 set { description = value; }
119 private ASN1 AlgorithmIdentifier (string oid)
121 ASN1 ai = new ASN1 (0x30);
122 ai.Add (ASN1Convert.FromOid (oid));
123 ai.Add (new ASN1 (0x05)); // NULL
127 private ASN1 Attribute (string oid, ASN1 value)
129 ASN1 attr = new ASN1 (0x30);
130 attr.Add (ASN1Convert.FromOid (oid));
131 ASN1 aset = attr.Add (new ASN1 (0x31));
136 private ASN1 Opus (string description, string url)
138 ASN1 opus = new ASN1 (0x30);
139 if (description != null) {
140 ASN1 part1 = opus.Add (new ASN1 (0xA0));
141 part1.Add (new ASN1 (0x80, Encoding.BigEndianUnicode.GetBytes (description)));
144 ASN1 part2 = opus.Add (new ASN1 (0xA1));
145 part2.Add (new ASN1 (0x80, Encoding.ASCII.GetBytes (url)));
151 // private const string rsaEncryption = "1.2.840.113549.1.1.1";
153 // private const string data = "1.2.840.113549.1.7.1";
154 private const string signedData = "1.2.840.113549.1.7.2";
156 // private const string contentType = "1.2.840.113549.1.9.3";
157 // private const string messageDigest = "1.2.840.113549.1.9.4";
158 private const string countersignature = "1.2.840.113549.1.9.6";
159 // microsoft spc (software publisher certificate)
160 private const string spcStatementType = "1.3.6.1.4.1.311.2.1.11";
161 private const string spcSpOpusInfo = "1.3.6.1.4.1.311.2.1.12";
162 private const string spcPelmageData = "1.3.6.1.4.1.311.2.1.15";
163 // private const string individualCodeSigning = "1.3.6.1.4.1.311.2.1.21";
164 private const string commercialCodeSigning = "1.3.6.1.4.1.311.2.1.22";
165 private const string timestampCountersignature = "1.3.6.1.4.1.311.3.2.1";
167 //private static byte[] version = { 0x01 };
168 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 };
170 private byte[] Header (byte[] fileHash, string hashAlgorithm)
172 string hashOid = CryptoConfig.MapNameToOID (hashAlgorithm);
173 ASN1 content = new ASN1 (0x30);
174 ASN1 c1 = content.Add (new ASN1 (0x30));
175 c1.Add (ASN1Convert.FromOid (spcPelmageData));
176 c1.Add (new ASN1 (0x30, obsolete));
177 ASN1 c2 = content.Add (new ASN1 (0x30));
178 c2.Add (AlgorithmIdentifier (hashOid));
179 c2.Add (new ASN1 (0x04, fileHash));
181 pkcs7.HashName = hashAlgorithm;
182 pkcs7.Certificates.AddRange (certs);
183 pkcs7.ContentInfo.ContentType = spcIndirectDataContext;
184 pkcs7.ContentInfo.Content.Add (content);
186 pkcs7.SignerInfo.Certificate = certs [0];
187 pkcs7.SignerInfo.Key = rsa;
191 opus = Attribute (spcSpOpusInfo, Opus (description, null));
193 opus = Attribute (spcSpOpusInfo, Opus (description, url.ToString ()));
194 pkcs7.SignerInfo.AuthenticatedAttributes.Add (opus);
195 // When using the MS Root Agency (test) we can't include this attribute in the signature or it won't validate!
196 // pkcs7.SignerInfo.AuthenticatedAttributes.Add (Attribute (spcStatementType, new ASN1 (0x30, ASN1Convert.FromOid (commercialCodeSigning).GetBytes ())));
197 pkcs7.GetASN1 (); // sign
198 return pkcs7.SignerInfo.Signature;
201 public ASN1 TimestampRequest (byte[] signature)
203 PKCS7.ContentInfo ci = new PKCS7.ContentInfo (PKCS7.Oid.data);
204 ci.Content.Add (new ASN1 (0x04, signature));
205 return PKCS7.AlgorithmIdentifier (timestampCountersignature, ci.ASN1);
208 public void ProcessTimestamp (byte[] response)
210 ASN1 ts = new ASN1 (Convert.FromBase64String (Encoding.ASCII.GetString (response)));
211 // first validate the received message
214 // add the supplied certificates inside our signature
215 for (int i=0; i < ts[1][0][3].Count; i++)
216 pkcs7.Certificates.Add (new X509Certificate (ts[1][0][3][i].GetBytes ()));
218 // add an unauthentified attribute to our signature
219 pkcs7.SignerInfo.UnauthenticatedAttributes.Add (Attribute (countersignature, ts[1][0][4][0]));
222 private byte[] Timestamp (byte[] signature)
224 ASN1 tsreq = TimestampRequest (signature);
225 WebClient wc = new WebClient ();
226 wc.Headers.Add ("Content-Type", "application/octet-stream");
227 wc.Headers.Add ("Accept", "application/octet-stream");
228 byte[] tsdata = Encoding.ASCII.GetBytes (Convert.ToBase64String (tsreq.GetBytes ()));
229 return wc.UploadData (timestamp.ToString (), tsdata);
232 private bool Save (string fileName, byte[] asn)
235 using (FileStream fs = File.Open (fileName + ".sig", FileMode.Create, FileAccess.Write)) {
236 fs.Write (asn, 0, asn.Length);
240 // someday I may be sure enough to move this into DEBUG ;-)
241 File.Copy (fileName, fileName + ".bak", true);
243 using (FileStream fs = File.Open (fileName, FileMode.Open, FileAccess.ReadWrite)) {
245 if (SecurityOffset > 0) {
246 // file was already signed, we'll reuse the position for the updated signature
247 filesize = SecurityOffset;
248 } else if (CoffSymbolTableOffset > 0) {
249 // strip (deprecated) COFF symbol table
250 fs.Seek (PEOffset + 12, SeekOrigin.Begin);
251 for (int i = 0; i < 8; i++)
253 // we'll put the Authenticode signature at this same place (just after the last section)
254 filesize = CoffSymbolTableOffset;
256 // file was never signed, nor does it contains (deprecated) COFF symbols
257 filesize = (int)fs.Length;
259 // must be a multiple of 8 bytes
260 int addsize = (filesize & 7);
262 addsize = 8 - addsize;
264 // IMAGE_DIRECTORY_ENTRY_SECURITY (offset, size)
265 byte[] data = BitConverterLE.GetBytes (filesize + addsize);
266 fs.Seek (PEOffset + 152, SeekOrigin.Begin);
267 fs.Write (data, 0, 4);
268 int size = asn.Length + 8;
269 int addsize_signature = (size & 7);
270 if (addsize_signature > 0)
271 addsize_signature = 8 - addsize_signature;
272 data = BitConverterLE.GetBytes (size + addsize_signature);
273 fs.Seek (PEOffset + 156, SeekOrigin.Begin);
274 fs.Write (data, 0, 4);
275 fs.Seek (filesize, SeekOrigin.Begin);
276 // align certificate entry to a multiple of 8 bytes
278 byte[] fillup = new byte[addsize];
279 fs.Write (fillup, 0, fillup.Length);
281 fs.Write (data, 0, data.Length); // length (again)
282 data = BitConverterLE.GetBytes (0x00020200); // magic
283 fs.Write (data, 0, data.Length);
284 fs.Write (asn, 0, asn.Length);
285 if (addsize_signature > 0) {
286 byte[] fillup = new byte[addsize_signature];
287 fs.Write (fillup, 0, fillup.Length);
294 public bool Sign (string fileName)
299 HashAlgorithm hash = HashAlgorithm.Create (Hash);
300 // 0 to 215 (216) then skip 4 (checksum)
302 byte[] digest = GetHash (hash);
303 byte[] signature = Header (digest, Hash);
304 if (timestamp != null) {
305 byte[] ts = Timestamp (signature);
306 // add timestamp information inside the current pkcs7 SignedData instance
307 // (this is possible because the data isn't yet signed)
308 ProcessTimestamp (ts);
311 PKCS7.ContentInfo sign = new PKCS7.ContentInfo (signedData);
312 sign.Content.Add (pkcs7.ASN1);
313 authenticode = sign.ASN1;
316 return Save (fileName, authenticode.GetBytes ());
318 catch (Exception e) {
319 Console.WriteLine (e);
324 // in case we just want to timestamp the file
325 public bool Timestamp (string fileName)
328 AuthenticodeDeformatter def = new AuthenticodeDeformatter (fileName);
329 byte[] signature = def.Signature;
330 if (signature != null) {
332 PKCS7.ContentInfo ci = new PKCS7.ContentInfo (signature);
333 pkcs7 = new PKCS7.SignedData (ci.Content);
335 byte[] response = Timestamp (pkcs7.SignerInfo.Signature);
336 ASN1 ts = new ASN1 (Convert.FromBase64String (Encoding.ASCII.GetString (response)));
337 // insert new certificates and countersignature into the original signature
338 ASN1 asn = new ASN1 (signature);
339 ASN1 content = asn.Element (1, 0xA0);
343 ASN1 signedData = content.Element (0, 0x30);
344 if (signedData == null)
347 // add the supplied certificates inside our signature
348 ASN1 certificates = signedData.Element (3, 0xA0);
349 if (certificates == null) {
350 certificates = new ASN1 (0xA0);
351 signedData.Add (certificates);
353 for (int i = 0; i < ts[1][0][3].Count; i++) {
354 certificates.Add (ts[1][0][3][i]);
357 // add an unauthentified attribute to our signature
358 ASN1 signerInfoSet = signedData[signedData.Count - 1];
359 ASN1 signerInfo = signerInfoSet[0];
360 ASN1 unauthenticated = signerInfo[signerInfo.Count - 1];
361 if (unauthenticated.Tag != 0xA1) {
362 unauthenticated = new ASN1 (0xA1);
363 signerInfo.Add (unauthenticated);
365 unauthenticated.Add (Attribute (countersignature, ts[1][0][4][0]));
367 return Save (fileName, asn.GetBytes ());
370 catch (Exception e) {
371 Console.WriteLine (e);