diff --git a/tools/xml_dsig_tool/README.md b/tools/xml_dsig_tool/README.md new file mode 100644 index 00000000..21a1de6d --- /dev/null +++ b/tools/xml_dsig_tool/README.md @@ -0,0 +1,30 @@ +The xml_dsig_tool is a Windows command line application that provides the ability to perform basic cryptographic functions per the W3C XML Signature Syntax and Processing Version 1.1. The functions include: + +sign : append an enveloped signature to an unsigned XML document +validate : validate a signed base rim's signature (NOTE: cryptographic validation only, this tool does not validate the RIM structure) + + +# Build and package + - Install Visual Studio + - The recommended project name is "xml_dsig_tool" so that the resulting executable file will be appropriately named xml_dsig_tool.exe. + - Install NuGet packages: + - System.CommandLine.2.0.0-beta4 (check "Include Prerelease" next to search bar) + - System.Security.Cryptography.X509Certificates + - System.Security.Cryptography.Xml + - Publish executable + - https://docs.microsoft.com/en-us/dotnet/core/tutorials/publishing-with-visual-studio?pivots=dotnet-6-0 + - Install support files to .exe directory + - privateRimKey.pem + - RimSignCert.pem + - unsigned.xml + + +# Running xml_dsig_tool +Navigate to the .exe directory and run the following commands + +help + +sign --file unsigned.xml --private-key privateKey.pem + +validate --file signed_unsigned.xml --certificate RimSignCert.pem + diff --git a/tools/xml_dsig_tool/RimSignCert.pem b/tools/xml_dsig_tool/RimSignCert.pem new file mode 100644 index 00000000..40aa4386 --- /dev/null +++ b/tools/xml_dsig_tool/RimSignCert.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID2jCCAsKgAwIBAgIJAP0uwoNdwZDFMA0GCSqGSIb3DQEBCwUAMFMxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJWQTEQMA4GA1UECgwHRXhhbXBsZTERMA8GA1UECwwI +UENDbGllbnQxEjAQBgNVBAMMCUV4YW1wbGVDQTAeFw0yMDA3MjEyMTQ1MDBaFw0z +MDA1MzAyMTQ1MDBaMFwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJWQTEQMA4GA1UE +CgwHRXhhbXBsZTERMA8GA1UECwwIUENDbGllbnQxGzAZBgNVBAMMEmV4YW1wbGUu +UklNLnNpZ25lcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKd1lWGk +SRuxAAY2wHag2GVxUk1dZx2PTpfQOflvLeccAVwa8mQhlsRERq+QK8ilj8Xfqs44 +/nBaccZDOjdfIxIUCMfwhGXjxCaqZbgTucNsExDnu4arTGraoAwzHg0cVLiKT/Cx +j9NL4dcMgxRXsPdHfXb0923C7xYd2t2qfW05umgaj7qeQl6c68CFNsGX4JA8rWFQ +ZvvGx5DGlK4KTcjPuQQINs5fxasNKqLY2hq+z82x/rqwr2hmyizD6FpFSyIABPEM +PfB036GEhRwu1WEMkq8yIp2jgRUoFYke9pB3ph9pVow0Hh4mNFSKD4pP41VSKY1n +us83mdkuukPy5o0CAwEAAaOBpzCBpDAdBgNVHQ4EFgQUL96459AwoiCdqgGGGpZP +7ezyvMEwHwYDVR0jBBgwFoAURqG47dumcV/Q0ud6ijxdbprDljgwCQYDVR0TBAIw +ADALBgNVHQ8EBAMCBsAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwNQYIKwYBBQUHAQEE +KTAnMCUGCCsGAQUFBzAChhlodHRwczovL2V4YW1wbGUuY29tL2NlcnRzMA0GCSqG +SIb3DQEBCwUAA4IBAQDpKx5oQlkS11cg7Qp58BmCvjCzFpof+qYePooJsD3i5SwK +fRTa2CkDMww9qrwBK7G60y7jhe5InKTdqIlVqaji5ZImR0QMKTtk7zt9AJ9EaEzK +xfDiE/qX34KxNe4ZmbvLH8N+BSujQXMMi56zGjW469Y/rbDMG8uU1dq3zqhO5b+d +Ur1ecdkYLgzxu6O+oWy5JpVibmcjvNezJsUtjc+km2FYm24vU3/fCNzZ2z0EHQES +cIEQ5OqfpdFrV3De238RhMH6J4xePSidnFpfBc6FrdyDI1A8eRFz36I4xfVL3ZnJ +P/+j+NE4q6yz5VGvm0npLO394ZihtsI1sRAR8ORJ +-----END CERTIFICATE----- diff --git a/tools/xml_dsig_tool/privateRimKey.pem b/tools/xml_dsig_tool/privateRimKey.pem new file mode 100644 index 00000000..afe282c4 --- /dev/null +++ b/tools/xml_dsig_tool/privateRimKey.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCndZVhpEkbsQAG +NsB2oNhlcVJNXWcdj06X0Dn5by3nHAFcGvJkIZbEREavkCvIpY/F36rOOP5wWnHG +Qzo3XyMSFAjH8IRl48QmqmW4E7nDbBMQ57uGq0xq2qAMMx4NHFS4ik/wsY/TS+HX +DIMUV7D3R3129Pdtwu8WHdrdqn1tObpoGo+6nkJenOvAhTbBl+CQPK1hUGb7xseQ +xpSuCk3Iz7kECDbOX8WrDSqi2Noavs/Nsf66sK9oZsosw+haRUsiAATxDD3wdN+h +hIUcLtVhDJKvMiKdo4EVKBWJHvaQd6YfaVaMNB4eJjRUig+KT+NVUimNZ7rPN5nZ +LrpD8uaNAgMBAAECggEAcnG8npd9U0x7HMQMcsZoPaPdwHvF/gCzkLNA+8RM1bZh +A4ZzA5WlCQs0V8Wq9pyXjn7Wp8txsG1PdlT5k2AUgsVoXuR0R4IKyvYHQG9StEjH +GvWURmwJdLlnSg8hSYqEJ/52taNUDO6+MI8fgiaQDd8w0ryF4OCpLy9GJdnfkGYZ +Ayemb3USFUdj/S67NVqxnvAfFMM5FqkKGhkoy7wBRgO6eOeJvoTq8LMiPiponwwF +DW409ZStbrk1f1Oszst/UvFUWA9BdDfeoPmFR61y3eB5zlMQG8Mhr2v5hvkj9TPX +FU4Fm4EzZ1h/60cdWoP6XYCP7F2NqZ8N8u4UBQNAIQKBgQDcGIw5GJEvRF+FFTTR +hYatMRn80DGTVjdT32MgajdKx05OWxBmQsFob34fiSnr0wAXPJeDXG4ruMBE2bSk +EC8rCO08G8ihQoH8x0cvuERe1fpVWk3RWNucVGIiJSEXAIwWrlYZLTfYd5GqBkPE +OQxxo4MtOyqeHmVH1mOywk9ABQKBgQDCxt95luzqQZV9Xl78QQvOIbjOdHLjY23Z +yp8sGt9birL/WZ33TCRgmH1e61BdrSqO7Om/ail2Y59XM5UU6kLbDj0IgmOPTsrJ +JmIVf8r3bKltVUaLePgr4yex7dmtHRH8OkLXKnE0RCO0kCi9kJMB12yE3pWxk+Pu +zztQd3a66QKBgBNJd2g9deONe01fOVyu9clRhzR3ThDaOkj4R2h8xlGgO4V0R3Ce +ovIy6vt6epj2yYg/wAs720+rhfXCmijSXj/ILXnZ+W/gMyHimKNe42boG2LFYhJZ +Vg1R+7OAS3EHlD8ckeDs7Hrkp3gdymx0j1mZ+ZHKIIbwpPFxoRT2IBm9AoGBAI0Z +bIK0puP8psKvPrgWluq42xwUl7XKLaX8dtqIjQ3PqGP7E8g2TJP9Y7UDWrDB5Xas +gZi821R8Ts3o/DKukcgGxIgJjP4f4h9dwug4L1yWRxaBFB2tgHqqj/MBjxMtX/4M +Zqdgg6mNQyBm3lyVAynuWRrX9DE0JYa2cQ2VvVkhAoGBAMBv/oT813w00759PmkO +Uxv3LXTJuYBbq0Rmga25jN3ow8LrGQdSVg7F/af3I5KUF7mLiegDy1pkRfauyXH7 ++WhEqnf86vDrzPpytDMxinWOQZusCqeWHb+nuVTuL3Fv+GxEdwVGYI/7lFJ7B//h +P5rU93ZoYY7sWcGVqaaEkMRU +-----END PRIVATE KEY----- diff --git a/tools/xml_dsig_tool/unsigned.xml b/tools/xml_dsig_tool/unsigned.xml new file mode 100644 index 00000000..1fa11f85 --- /dev/null +++ b/tools/xml_dsig_tool/unsigned.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/tools/xml_dsig_tool/xmlDsig.cs b/tools/xml_dsig_tool/xmlDsig.cs new file mode 100644 index 00000000..8add8088 --- /dev/null +++ b/tools/xml_dsig_tool/xmlDsig.cs @@ -0,0 +1,249 @@ +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Security.Cryptography.Xml; +using System.Xml; +using System.CommandLine; +using System.Text; + +/** + * This command line program has three commands: + * 1. sign - append a signature calculated from a user-provided private key + * 2. validate - validate a signature with a user-provided certificate + * 3. debug - print out important components of a signed XML document + * + * The validate functioin strictly checks the cryptographic integrity of the signature, + * it does not verify the integrity of the certificate chain. + */ +class XmlDsigTool +{ + + static async Task Main(String[] args) + { + var fileOption = new Option( + name: "--file", + description: "The filename input for the command.", + parseArgument: result => + { + string? filePath = result.Tokens.Single().Value; + if (!File.Exists(filePath)) + { + result.ErrorMessage = "File " + filePath + " does not exist."; + return null; + } + else + { + return filePath; + } + }); + var privateKeyOption = new Option( + name: "--private-key", + description: "The private key with which to sign." + ); + var certificateOption = new Option( + name: "--certificate", + description: "The certificate with which to validate the signature." + ); + + var rootCommand = new RootCommand("A tool for signing, validating, and debugging base RIMs."); + var signCommand = new Command("sign", "Sign the given file with the given key.") + { + fileOption, + privateKeyOption + }; + var validateCommand = new Command("validate", "Validate the signature in the given base RIM.") + { + fileOption, + certificateOption + }; + var debugCommand = new Command("debug", "Print out the significant portions of a base RIM and the expected signature value.") + { + fileOption, + privateKeyOption + }; + + signCommand.SetHandler(async (file, privateKey) => + { + await SignXml(file, privateKey); + }, fileOption, privateKeyOption); + validateCommand.SetHandler(async (file, certificate) => + { + await ValidateXml(file, certificate); + }, fileOption, certificateOption); + debugCommand.SetHandler(async (file, privateKey) => + { + await DebugRim(file, privateKey); + }, fileOption, privateKeyOption); + + rootCommand.AddCommand(signCommand); + rootCommand.AddCommand(validateCommand); + rootCommand.AddCommand(debugCommand); + + return rootCommand.InvokeAsync(args).Result; + } + internal static async Task SignXml(string xmlFilename, string keyFilename) + { + if (String.IsNullOrWhiteSpace(xmlFilename)) + throw new ArgumentException(nameof(xmlFilename)); + if (String.IsNullOrWhiteSpace(keyFilename)) + throw new ArgumentException(nameof(keyFilename)); + + Console.Write("Signing xml..."); + + // Load an XML file into a SignedXML object. + XmlDocument unsignedDoc = new XmlDocument(); + unsignedDoc.Load(xmlFilename); + SignedXml signedXml = new SignedXml(unsignedDoc); + + //Load private key from file + string privateKeyText = File.ReadAllText(keyFilename); + var privateKey = RSA.Create(); + privateKey.ImportFromPem(privateKeyText); + + // Add the key to the SignedXml document. + signedXml.SigningKey = privateKey; + + // 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); + + // Add keyinfo block + KeyInfo keyInfo = new KeyInfo(); + keyInfo.AddClause(new RSAKeyValue((RSA)privateKey)); + signedXml.KeyInfo = keyInfo; + + // 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. + unsignedDoc.DocumentElement.AppendChild(unsignedDoc.ImportNode(xmlDigitalSignature, true)); + string signedFilename = "signed_" + xmlFilename; + unsignedDoc.Save(signedFilename); + Console.WriteLine("Xml signed and written to " + signedFilename); + } + + // Verify the signature of an XML file against an asymmetric + // algorithm and return the result. + internal static async Task ValidateXml(string signedFilename, string certFilename) + { + // Check arguments. + if (String.IsNullOrWhiteSpace(signedFilename)) + throw new ArgumentException(nameof(signedFilename)); + if (certFilename == null) + throw new ArgumentException(nameof(certFilename)); + + Console.Write("Verifying signature..."); + // Create a new SignedXml object and pass it + // the XML document class. + XmlDocument signedDoc = new XmlDocument(); + signedDoc.Load(signedFilename); + SignedXml signedXml = new SignedXml(signedDoc); + + //Load public cert from file + X509Certificate2 signingCert = new X509Certificate2(certFilename); + RSA publicKey = signingCert.GetRSAPublicKey(); + + // Find the "Signature" node and create a new + // XmlNodeList object. + XmlNodeList nodeList = signedDoc.GetElementsByTagName("Signature"); + + // Throw an exception if no signature was found. + if (nodeList.Count <= 0) + { + throw new CryptographicException("Verification failed: No Signature was found in the document."); + } + + // This example only supports one signature for + // the entire XML document. Throw an exception + // if more than one signature was found. + if (nodeList.Count >= 2) + { + throw new CryptographicException("Verification failed: More than one signature was found for the document."); + } + + // Load the first node. + signedXml.LoadXml((XmlElement)nodeList[0]); + Boolean isValid = false; + try + { + isValid = signedXml.CheckSignature(publicKey); + } + catch (Exception e) + { + Console.WriteLine(e.Message); + } + + // Check the signature and return the result. + if (isValid) + { + Console.WriteLine("Signature is valid!"); + } + else + { + Console.WriteLine("Signature is not valid."); + } + } + + internal static async Task DebugRim(string filename, string keyFilename) + { + if (String.IsNullOrWhiteSpace(filename)) + { + throw new ArgumentException(nameof(filename)); + } else if (String.IsNullOrWhiteSpace(keyFilename)) + { + throw new ArgumentException(nameof(keyFilename)); + } + + XmlDocument xmlToBeSigned = new XmlDocument(); + XmlDocument xmlDoc = new XmlDocument(); + xmlDoc.Load(filename); + XmlNodeList nodes = xmlDoc.GetElementsByTagName("SoftwareIdentity"); + XmlNodeList signatureNodes = xmlDoc.GetElementsByTagName("Signature"); + + //Assumes there is only one signature; may change in the future for multiple signatures + if (signatureNodes.Count > 0) + { + nodes[0].RemoveChild(signatureNodes[0]); + } + xmlToBeSigned.AppendChild(xmlToBeSigned.ImportNode(nodes[0], true)); + string outFileName = "ToBeSigned_" + filename; + xmlToBeSigned.Save(outFileName); + Console.WriteLine("Xml data to be signed parsed to " + outFileName); + + //Load private key from file + string privateKeyText = File.ReadAllText(keyFilename); + var privateKey = RSA.Create(); + privateKey.ImportFromPem(privateKeyText); + + // Add the key to the SignedXml document. + SignedXml signedXml = new SignedXml(xmlToBeSigned); + signedXml.SigningKey = privateKey; + + // 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); + + signedXml.ComputeSignature(); + Signature signature = signedXml.Signature; + Console.WriteLine("For the data to be signed the expected signature value is " + + Encoding.Default.GetString(signature.SignatureValue)); + } + +}