//
// SignCode.cs: secutil clone tool
//
// Author:
//	Sebastien Pouliot (spouliot@motus.com)
//
// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
//

using System;
using System.Reflection;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;

using Mono.Security.Authenticode;

[assembly: AssemblyTitle("Mono SignCode")]
[assembly: AssemblyDescription("Sign assemblies and PE files using Authenticode(tm).")]

namespace Mono.Tools {

	class SignCode {

		static private void Header () 
		{
			Assembly a = Assembly.GetExecutingAssembly ();
			AssemblyName an = a.GetName ();

			object [] att = a.GetCustomAttributes (typeof (AssemblyTitleAttribute), false);
			string title = ((att.Length > 0) ? ((AssemblyTitleAttribute) att [0]).Title : "Mono SignCode");

			att = a.GetCustomAttributes (typeof (AssemblyCopyrightAttribute), false);
			string copyright = ((att.Length > 0) ? ((AssemblyCopyrightAttribute) att [0]).Copyright : "");

			Console.WriteLine ("{0} {1}", title, an.Version.ToString ());
			Console.WriteLine ("{0}{1}", copyright, Environment.NewLine);
		}

		static private void Help () 
		{
			Console.WriteLine ("Usage: signcode [options] filename{0}", Environment.NewLine);
			Console.WriteLine ("\t-spc spc\tSoftware Publisher Certificate file");
			Console.WriteLine ("\t-v pvk\tPrivate Key file");
			Console.WriteLine ("\t-a md5 | sha1\tHash Algorithm (default: MD5)");
			Console.WriteLine ("\t-$ indivisual | commercial\tSignature type");
			Console.WriteLine ("\t-n\tDescription for the signed file");
			Console.WriteLine ("\t-i\tURL for the signed file");
			Console.WriteLine ("Timestamp options");
			Console.WriteLine ("\t-t url\tTimestamp service http URL");
			Console.WriteLine ("\t-tr #\tNumber of retries for timestamp");
			Console.WriteLine ("\t-tw #\tDelay between retries");
			Console.WriteLine ("\t-x\tOnly timestamp (no signature)");
			Console.WriteLine ("CSP options");
			Console.WriteLine ("\t-k name\tKey Container Name");
			Console.WriteLine ("\t-p name\tProvider Name");
			Console.WriteLine ("\t-y #\tProvider Type");
			Console.WriteLine ("\t-ky [signature|exchange|#]\tKey Type");
			Console.WriteLine ("\t-r [localMachine|currentUser]\tKey Location");
		}

		[STAThread]
		static int Main(string[] args)
		{
			Header();
			if (args.Length < 1) {
				Help ();
				return 1;
			}

			CspParameters csp = new CspParameters ();
			string pvkFilename = null;
			string spcFilename = null;
			int timestampRetry = 0;
			int timestampDelay = 0;
			bool sign = true;

			// to be signed
			string tbsFilename = args [args.Length - 1];

			AuthenticodeFormatter af = new AuthenticodeFormatter ();

			int i = 0;
			while (i < args.Length) {
				switch (args[i++]) {
					case "-spc":
						spcFilename = args [i++];
						break;
					case "-v":
						pvkFilename = args [i++];
						break;
					case "-a":
						af.Hash = args [i++];
						break;
					case "-$":
						string auth = args [i++].ToLower ();
						switch (auth) {
							case "individual":
								af.Authority = Authority.Commercial;
								break;
							case "commercial":
								af.Authority = Authority.Individual;
								break;
							default:
								Console.WriteLine ("Unknown authority {0}", auth);
								return 1;
						}
						break;
					case "-i":
						af.Description = args [i++];
						break;
					case "-n":
						af.URL = args [i++];
						break;
					// timestamp options
					case "-t":
						af.TimestampURL = args [i++];
						break;
					case "-tr":
						timestampRetry = Convert.ToInt32 (args [i++]);
						break;
					case "-tw":
						timestampDelay = Convert.ToInt32 (args [i++]) * 1000;
						break;
					case "-x":
						// only timestamp
						sign = false;  
						break;
					// CSP provider options
					case "-k":
						csp.KeyContainerName = args [i++];
						break;
					case "-p":
						csp.ProviderName = args [i++];
						break;
					case "-y":
						csp.ProviderType = Convert.ToInt32 (args [i++]);
						break;
					case "-ky":
						string key = args [i++];
						switch (key) {
							case "signature":
								csp.KeyNumber = 0;
								break;
							case "exchange":
								csp.KeyNumber = 0;
								break;
							default:
								csp.KeyNumber = Convert.ToInt32 (key);
								break;
						}
						break;
					case "-r":
						string location = args [i++];
						switch (location) {
							case "localMachine":
								csp.Flags = CspProviderFlags.UseMachineKeyStore;
								break;
							case "currentUser":
								csp.Flags = CspProviderFlags.UseDefaultKeyContainer;
								break;
							default:
								Console.WriteLine ("Unknown location {0}", location);
								return 1;
						}
						break;
					// unsupported options
					case "-j":
					case "-jp":
						Console.WriteLine ("Unsupported option {0}", args[i-1]);
						return 1;
				}
			}

			if (sign) {
				RSACryptoServiceProvider rsa = new RSACryptoServiceProvider (csp);
				if (pvkFilename != null) {
					PrivateKey pvk = PrivateKey.CreateFromFile (pvkFilename);
					if (pvk.Encrypted) {
						Console.WriteLine ("Enter password for {0}: ", pvkFilename);
						string pvkPassword = Console.ReadLine ();
						pvk = PrivateKey.CreateFromFile (pvkFilename, pvkPassword);
					}
					if (pvk.RSA != null) {
						rsa.ImportParameters (pvk.RSA.ExportParameters (true));
					}
					else {
						Console.WriteLine ("Invalid password for {0}", pvkFilename);
						return 1;
					}
				}

				SoftwarePublisherCertificate spc = null;
				if (spcFilename != null) {
					spc = SoftwarePublisherCertificate.CreateFromFile (spcFilename);
					af.Certificates.AddRange (spc.Certificates);
					// TODO CRL
				}

				af.RSA = rsa;
				af.Sign (tbsFilename);
			}
/* TODO
			if (af.TimestampURL != null) {
				for (int j=0; j < timestampRetry + 1; j++) {
					if (!af.Timestamp (tbsFilename)) {
						Thread.Sleep (timestampDelay);
						continue;
					}
					break;
				}
			}*/
			return 0;
		}
	}
}
