2 // AuthenticodeFormatter.cs: Authenticode signature generator
5 // Sebastien Pouliot <sebastien@ximian.com>
7 // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
8 // (C) 2004 Novell (http://www.novell.com)
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using System.Collections;
34 using System.Globalization;
36 using System.Security.Cryptography;
41 using Mono.Security.X509;
43 namespace Mono.Security.Authenticode {
45 public class AuthenticodeFormatter : AuthenticodeBase {
47 private Authority authority;
48 private X509CertificateCollection certs;
49 private ArrayList crls;
52 private Uri timestamp;
53 private ASN1 authenticode;
54 private PKCS7.SignedData pkcs7;
55 private string description;
58 public AuthenticodeFormatter () : base ()
60 certs = new X509CertificateCollection ();
61 crls = new ArrayList ();
62 authority = Authority.Maximum;
63 pkcs7 = new PKCS7.SignedData ();
66 public Authority Authority {
67 get { return authority; }
68 set { authority = value; }
71 public X509CertificateCollection Certificates {
75 public ArrayList Crl {
87 throw new ArgumentNullException ("Hash");
89 string h = value.ToUpper (CultureInfo.InvariantCulture);
96 throw new ArgumentException ("Invalid Authenticode hash algorithm");
106 public Uri TimestampUrl {
107 get { return timestamp; }
108 set { timestamp = value; }
111 public string Description {
112 get { return description; }
113 set { description = value; }
121 private ASN1 AlgorithmIdentifier (string oid)
123 ASN1 ai = new ASN1 (0x30);
124 ai.Add (ASN1Convert.FromOid (oid));
125 ai.Add (new ASN1 (0x05)); // NULL
129 private ASN1 Attribute (string oid, ASN1 value)
131 ASN1 attr = new ASN1 (0x30);
132 attr.Add (ASN1Convert.FromOid (oid));
133 ASN1 aset = attr.Add (new ASN1 (0x31));
138 private ASN1 Opus (string description, string url)
140 ASN1 opus = new ASN1 (0x30);
141 if (description != null) {
142 ASN1 part1 = opus.Add (new ASN1 (0xA0));
143 part1.Add (new ASN1 (0x80, Encoding.BigEndianUnicode.GetBytes (description)));
146 ASN1 part2 = opus.Add (new ASN1 (0xA1));
147 part2.Add (new ASN1 (0x80, Encoding.ASCII.GetBytes (url)));
153 private const string rsaEncryption = "1.2.840.113549.1.1.1";
155 private const string data = "1.2.840.113549.1.7.1";
156 private const string signedData = "1.2.840.113549.1.7.2";
158 private const string contentType = "1.2.840.113549.1.9.3";
159 private const string messageDigest = "1.2.840.113549.1.9.4";
160 private const string countersignature = "1.2.840.113549.1.9.6";
161 // microsoft spc (software publisher certificate)
162 private const string spcStatementType = "1.3.6.1.4.1.311.2.1.11";
163 private const string spcSpOpusInfo = "1.3.6.1.4.1.311.2.1.12";
164 private const string spcPelmageData = "1.3.6.1.4.1.311.2.1.15";
165 private const string individualCodeSigning = "1.3.6.1.4.1.311.2.1.21";
166 private const string commercialCodeSigning = "1.3.6.1.4.1.311.2.1.22";
167 private const string timestampCountersignature = "1.3.6.1.4.1.311.3.2.1";
169 private static byte[] version = { 0x01 };
170 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 };
172 private byte[] Header (byte[] fileHash, string hashAlgorithm)
174 string hashOid = CryptoConfig.MapNameToOID (hashAlgorithm);
175 ASN1 content = new ASN1 (0x30);
176 ASN1 c1 = content.Add (new ASN1 (0x30));
177 c1.Add (ASN1Convert.FromOid (spcPelmageData));
178 c1.Add (new ASN1 (0x30, obsolete));
179 ASN1 c2 = content.Add (new ASN1 (0x30));
180 c2.Add (AlgorithmIdentifier (hashOid));
181 c2.Add (new ASN1 (0x04, fileHash));
183 pkcs7.HashName = hashAlgorithm;
184 pkcs7.Certificates.AddRange (certs);
185 pkcs7.ContentInfo.ContentType = spcIndirectDataContext;
186 pkcs7.ContentInfo.Content.Add (content);
188 pkcs7.SignerInfo.Certificate = certs [0];
189 pkcs7.SignerInfo.Key = rsa;
190 pkcs7.SignerInfo.AuthenticatedAttributes.Add (Attribute (spcSpOpusInfo, Opus (description, url.ToString ())));
191 pkcs7.SignerInfo.AuthenticatedAttributes.Add (Attribute (contentType, ASN1Convert.FromOid (spcIndirectDataContext)));
192 pkcs7.SignerInfo.AuthenticatedAttributes.Add (Attribute (spcStatementType, new ASN1 (0x30, ASN1Convert.FromOid (commercialCodeSigning).GetBytes ())));
194 ASN1 temp = pkcs7.ASN1; // sign
195 return pkcs7.SignerInfo.Signature;
198 public ASN1 TimestampRequest (byte[] signature)
200 PKCS7.ContentInfo ci = new PKCS7.ContentInfo (PKCS7.Oid.data);
201 ci.Content.Add (new ASN1 (0x04, signature));
202 return PKCS7.AlgorithmIdentifier (timestampCountersignature, ci.ASN1);
205 public void ProcessTimestamp (byte[] response)
207 ASN1 ts = new ASN1 (Convert.FromBase64String (Encoding.ASCII.GetString (response)));
208 // first validate the received message
211 // add the supplied certificates inside our signature
212 for (int i=0; i < ts[1][0][3].Count; i++)
213 pkcs7.Certificates.Add (new X509Certificate (ts[1][0][3][i].GetBytes ()));
215 // add an unauthentified attribute to our signature
216 pkcs7.SignerInfo.UnauthenticatedAttributes.Add (Attribute (countersignature, ts[1][0][4][0]));
219 public bool Sign (string fileName)
221 string hashAlgorithm = "MD5";
222 FileStream fs = new FileStream (fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
223 byte[] file = new byte [fs.Length];
224 fs.Read (file, 0, file.Length);
228 if (BitConverter.ToUInt16 (file, 0) != 0x5A4D)
231 // find offset of PE header
232 int peOffset = BitConverter.ToInt32 (file, 60);
233 if (peOffset > file.Length)
237 if (BitConverter.ToUInt16 (file, peOffset) != 0x4550)
240 // IMAGE_DIRECTORY_ENTRY_SECURITY
241 int dirSecurityOffset = BitConverter.ToInt32 (file, peOffset + 152);
242 int dirSecuritySize = BitConverter.ToInt32 (file, peOffset + 156);
244 if (dirSecuritySize > 8) {
245 rawData = new byte [dirSecuritySize - 8];
246 Buffer.BlockCopy (file, dirSecurityOffset + 8, rawData, 0, rawData.Length);
251 HashAlgorithm hash = HashAlgorithm.Create (hashAlgorithm);
252 // 0 to 215 (216) then skip 4 (checksum)
253 int pe = peOffset + 88;
254 hash.TransformBlock (file, 0, pe, file, 0);
256 // 220 to 279 (60) then skip 8 (IMAGE_DIRECTORY_ENTRY_SECURITY)
257 hash.TransformBlock (file, pe, 60, file, pe);
259 // 288 to end of file
260 int n = file.Length - pe;
261 // minus any authenticode signature (with 8 bytes header)
262 if (dirSecurityOffset != 0)
263 n -= (dirSecuritySize);
264 hash.TransformFinalBlock (file, pe, n);
267 byte[] signature = Header (hash.Hash, hashAlgorithm);
268 if (timestamp != null) {
269 ASN1 tsreq = TimestampRequest (signature);
270 WebClient wc = new WebClient ();
271 wc.Headers.Add ("Content-Type", "application/octet-stream");
272 wc.Headers.Add ("Accept", "application/octet-stream");
273 byte[] tsdata = Encoding.ASCII.GetBytes (Convert.ToBase64String (tsreq.GetBytes ()));
274 byte[] tsres = wc.UploadData (timestamp.ToString (), tsdata);
275 ProcessTimestamp (tsres);
277 PKCS7.ContentInfo sign = new PKCS7.ContentInfo (signedData);
278 sign.Content.Add (pkcs7.ASN1);
279 authenticode = sign.ASN1;
282 fs = File.Open (fileName + ".sig", FileMode.Create, FileAccess.Write);
283 byte[] asn = authenticode.GetBytes ();
284 fs.Write (asn, 0, asn.Length);
287 File.Copy (fileName, fileName + ".bak", true);
289 fs = File.Open (fileName, FileMode.Create, FileAccess.Write);
290 // IMAGE_DIRECTORY_ENTRY_SECURITY (offset, size)
291 byte[] data = BitConverter.GetBytes (file.Length);
292 file [peOffset + 152] = data [0];
293 file [peOffset + 153] = data [1];
294 file [peOffset + 154] = data [2];
295 file [peOffset + 155] = data [3];
296 int size = asn.Length + 8;
297 // must be a multiple of 8 bytes
298 int addsize = (size % 8);
300 addsize = 8 - addsize;
302 data = BitConverter.GetBytes (size); // header
303 file [peOffset + 156] = data [0];
304 file [peOffset + 157] = data [1];
305 file [peOffset + 158] = data [2];
306 file [peOffset + 159] = data [3];
307 fs.Write (file, 0, file.Length);
308 fs.Write (data, 0, data.Length); // length (again)
309 data = BitConverter.GetBytes (0x00020200); // magic
310 fs.Write (data, 0, data.Length);
311 fs.Write (asn, 0, asn.Length);
313 byte[] fillup = new byte [addsize];
314 fs.Write (fillup, 0, fillup.Length);
320 // in case we just want to timestamp the file
321 public bool Timestamp (string fileName)