context: //
  // AuthenticodeFormatter.cs: Authenticode signature generator
  //
  // Author:
  //	Sebastien Pouliot <sebastien@ximian.com>
  //
  // (C) 2003 Motus Technologies Inc. (http://www.motus.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
  // "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 CryptoSharp.Security;
  using CryptoSharp.Security.X509;
  
  namespace CryptoSharp.Security.Authenticode {
  
  	internal 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;
  
  		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);
  // 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;
  		}
  
  		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 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
  			byte[] file;
  			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 ();
  			}
  
  			// 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;
  				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
  					file[PEOffset + 12] = 0;
  					file[PEOffset + 13] = 0;
  					file[PEOffset + 14] = 0;
  					file[PEOffset + 15] = 0;
  					file[PEOffset + 16] = 0;
  					file[PEOffset + 17] = 0;
  					file[PEOffset + 18] = 0;
  					file[PEOffset + 19] = 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 = file.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);
  				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;
  				int addsize_signature = (size & 7);
  				if (addsize_signature > 0)
  					addsize_signature = 8 - addsize_signature;
  				data = BitConverterLE.GetBytes (size + addsize_signature);
  				file[PEOffset + 156] = data[0];
  				file[PEOffset + 157] = data[1];
  				file[PEOffset + 158] = data[2];
  
gt: 				file[PEOffset + 159] = data[3];