/* Copyright 2010,2011 Kevin Glynn (kevin.glynn@twigletsoftware.com) */ using System; using System.Reflection; using System.IO; using System.Xml.Serialization; using System.Xml; using System.Text; using NDesk.Options; using System.Collections.Generic; using System.Security.Cryptography; using System.Security.Cryptography.Xml; namespace Twiglet.CS2J.Utility { public class TemplateSigner { private const string CS2JSIGNER_VERSION = "pre-release"; private int Verbose { get; set; } public delegate void FileProcessor(string fName); private List<string> XmlDirs { get; set; } private List<string> ExcludeXmlDirs { get; set; } private String KeyFile = null; RSACryptoServiceProvider RsaKey { get; set; } public TemplateSigner() { Verbose = 0; XmlDirs = new List<string>(); ExcludeXmlDirs = new List<string>(); RsaKey = null; } private static void printUsageAndExit() { Console.Out.WriteLine("Usage: " + Path.GetFileNameWithoutExtension(System.Environment.GetCommandLineArgs()[0]) + " <type to dump, if not given dump all>"); Console.Out.WriteLine(" [-version] (show version information)"); Console.Out.WriteLine(" [-help|h|?] (this usage message)"); Console.Out.WriteLine(" [-v] (be [somewhat more] verbose, repeat for more verbosity)"); Console.Out.WriteLine(" [-privkeyfile <path to rsa private key file (xml format)"); Console.Out.WriteLine(" [-xmlpath <a root of xml files to be signed>] (can be multiple directories, separated by semi-colons)"); Console.Out.WriteLine(" [-exxmlpath <directories/files to be excluded from signing>] (can be multiple directories/files, separated by semi-colons)"); Console.Out.WriteLine(" [<root of xml files to be signed>] (can be multiple directories, separated by semi-colons)"); Environment.Exit(0); } private static void printVersion() { Console.Out.WriteLine(Path.GetFileNameWithoutExtension(System.Environment.GetCommandLineArgs()[0])); Console.WriteLine("Version: {0}", CS2JSIGNER_VERSION); } private static void addDirectories(IList<string> strs, string rawStr) { string[] argDirs = rawStr.Split(';'); for (int i = 0; i < argDirs.Length; i++) strs.Add(Path.GetFullPath(argDirs[i])); } public static void Main(string[] args) { TemplateSigner templateSigner = new TemplateSigner(); List<string> xmlDirs = new List<string>(); List<string> excludeXmlDirs = new List<string>(); OptionSet p = new OptionSet() .Add("v", v => templateSigner.Verbose++) .Add("version", v => printVersion()) .Add("help|h|?", v => printUsageAndExit()) .Add("keyfile=", fname => templateSigner.KeyFile = Path.GetFullPath(fname)) .Add("xmlpath=", dirs => addDirectories(xmlDirs, dirs)) .Add("exxmlpath=", dirs => addDirectories(excludeXmlDirs, dirs)) ; List<string> leftovers = p.Parse(args); if (leftovers != null) { foreach (string d in leftovers) { addDirectories(xmlDirs, d); } } templateSigner.XmlDirs = xmlDirs; templateSigner.ExcludeXmlDirs = excludeXmlDirs; if (!File.Exists(templateSigner.KeyFile)) { Console.Out.WriteLine("Error: RSA key at '" + templateSigner.KeyFile + "' not found, aborting."); Environment.Exit(1); } templateSigner.SignXmlFiles(); } public void SignXmlFiles() { try { XmlReader reader = XmlReader.Create(KeyFile); reader.MoveToContent(); // Create a new CspParameters object to specify // a key container. CspParameters cspParams = new CspParameters(); cspParams.KeyContainerName = "XML_DSIG_RSA_KEY"; // Initialise from . RsaKey = new RSACryptoServiceProvider(cspParams); RsaKey.PersistKeyInCsp = false; RsaKey.FromXmlString(reader.ReadOuterXml()); // Load .Net templates if (XmlDirs != null) { foreach (string r in XmlDirs) doFile(r, ".xml", SignNetTranslation, ExcludeXmlDirs); } } catch (Exception e) { Console.WriteLine(e.Message); } } private void SignNetTranslation(string filePath) { // Create a new XML document. XmlDocument xmlDoc = new XmlDocument(); // Load an XML file into the XmlDocument object. xmlDoc.PreserveWhitespace = true; xmlDoc.Load(filePath); // Sign the XML document. SignXml(xmlDoc, RsaKey); Console.WriteLine("XML file signed."); // Save the document. xmlDoc.Save(filePath); } // Sign an XML file. // This document cannot be verified unless the verifying // code has the key with which it was signed. private void SignXml(XmlDocument xmlDoc, RSA Key) { // Check arguments. if (xmlDoc == null) throw new ArgumentException("xmlDoc"); if (Key == null) throw new ArgumentException("Key"); // Add the namespace. XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDoc.NameTable); nsmgr.AddNamespace("ss", "http://www.w3.org/2000/09/xmldsig#"); // Remove any existing signature(s) XmlNode root = xmlDoc.DocumentElement; XmlNodeList nodeList = root.SelectNodes("/*/ss:Signature", nsmgr); for (int i = nodeList.Count - 1; i >= 0; i--) { nodeList[i].ParentNode.RemoveChild(nodeList[i]); } // Create a SignedXml object. SignedXml signedXml = new SignedXml(xmlDoc); // Add the key to the SignedXml document. signedXml.SigningKey = Key; // Create a reference to be signed. Reference reference = new Reference(); reference.Uri = ""; // Add an enveloped transformation to the reference. XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform(); reference.AddTransform(env); // Add the reference to the SignedXml object. signedXml.AddReference(reference); // Compute the signature. signedXml.ComputeSignature(); // Get the XML representation of the signature and save // it to an XmlElement object. XmlElement xmlDigitalSignature = signedXml.GetXml(); // Append the element to the XML document. xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(xmlDigitalSignature, true)); } // Call processFile on all files below f that have the given extension private void doFile(string root, string ext, FileProcessor processFile, IList<string> excludes) { string canonicalPath = Path.GetFullPath(root); // If this is a directory, walk each file/dir in that directory if (!excludes.Contains(canonicalPath.ToLower())) { if (Directory.Exists(canonicalPath)) { string[] files = Directory.GetFileSystemEntries(canonicalPath); for (int i = 0; i < files.Length; i++) doFile(Path.Combine(canonicalPath, files[i]), ext, processFile, excludes); } else if ((Path.GetFileName(canonicalPath).Length > ext.Length) && canonicalPath.Substring(canonicalPath.Length - ext.Length).Equals(ext)) { if (Verbose >= 2) Console.WriteLine(" " + canonicalPath); try { processFile(canonicalPath); } catch (Exception e) { Console.Error.WriteLine("\nCannot process file: " + canonicalPath); Console.Error.WriteLine("exception: " + e); } } } } } }