2022-07-18 16:57:27 +00:00
|
|
|
using System.Security.Cryptography;
|
|
|
|
using System.Security.Cryptography.X509Certificates;
|
|
|
|
using System.Security.Cryptography.Xml;
|
|
|
|
using System.Xml;
|
2022-07-28 11:46:54 +00:00
|
|
|
using System.CommandLine;
|
2022-08-11 15:59:46 +00:00
|
|
|
using System.Text;
|
2022-07-18 16:57:27 +00:00
|
|
|
|
2022-07-19 22:06:57 +00:00
|
|
|
/**
|
2022-07-28 11:46:54 +00:00
|
|
|
* 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,
|
2022-07-19 22:06:57 +00:00
|
|
|
* it does not verify the integrity of the certificate chain.
|
|
|
|
*/
|
2022-08-11 15:59:46 +00:00
|
|
|
class XmlDsigTool
|
2022-07-18 16:57:27 +00:00
|
|
|
{
|
|
|
|
|
2022-07-28 11:46:54 +00:00
|
|
|
static async Task<int> Main(String[] args)
|
2022-07-18 16:57:27 +00:00
|
|
|
{
|
2022-07-28 11:46:54 +00:00
|
|
|
var fileOption = new Option<string>(
|
|
|
|
name: "--file",
|
|
|
|
description: "The filename input for the command.",
|
2022-07-29 00:48:29 +00:00
|
|
|
parseArgument: result =>
|
2022-07-18 16:57:27 +00:00
|
|
|
{
|
2022-07-28 11:46:54 +00:00
|
|
|
string? filePath = result.Tokens.Single().Value;
|
|
|
|
if (!File.Exists(filePath))
|
|
|
|
{
|
|
|
|
result.ErrorMessage = "File " + filePath + " does not exist.";
|
|
|
|
return null;
|
2022-07-29 00:48:29 +00:00
|
|
|
}
|
|
|
|
else
|
2022-07-28 11:46:54 +00:00
|
|
|
{
|
|
|
|
return filePath;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
var privateKeyOption = new Option<string>(
|
|
|
|
name: "--private-key",
|
|
|
|
description: "The private key with which to sign."
|
|
|
|
);
|
|
|
|
var certificateOption = new Option<string>(
|
|
|
|
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.")
|
2022-07-18 16:57:27 +00:00
|
|
|
{
|
2022-07-28 11:46:54 +00:00
|
|
|
fileOption,
|
|
|
|
privateKeyOption
|
|
|
|
};
|
|
|
|
var validateCommand = new Command("validate", "Validate the signature in the given base RIM.")
|
|
|
|
{
|
|
|
|
fileOption,
|
|
|
|
certificateOption
|
|
|
|
};
|
2022-08-11 15:59:46 +00:00
|
|
|
var debugCommand = new Command("debug", "Print out the significant portions of a base RIM and the expected signature value.")
|
2022-07-28 11:46:54 +00:00
|
|
|
{
|
2022-08-11 15:59:46 +00:00
|
|
|
fileOption,
|
|
|
|
privateKeyOption
|
2022-07-28 11:46:54 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
signCommand.SetHandler(async (file, privateKey) =>
|
|
|
|
{
|
|
|
|
await SignXml(file, privateKey);
|
|
|
|
}, fileOption, privateKeyOption);
|
|
|
|
validateCommand.SetHandler(async (file, certificate) =>
|
|
|
|
{
|
|
|
|
await ValidateXml(file, certificate);
|
|
|
|
}, fileOption, certificateOption);
|
2022-08-11 15:59:46 +00:00
|
|
|
debugCommand.SetHandler(async (file, privateKey) =>
|
2022-07-28 11:46:54 +00:00
|
|
|
{
|
2022-08-11 15:59:46 +00:00
|
|
|
await DebugRim(file, privateKey);
|
|
|
|
}, fileOption, privateKeyOption);
|
2022-07-28 11:46:54 +00:00
|
|
|
|
|
|
|
rootCommand.AddCommand(signCommand);
|
|
|
|
rootCommand.AddCommand(validateCommand);
|
|
|
|
rootCommand.AddCommand(debugCommand);
|
|
|
|
|
|
|
|
return rootCommand.InvokeAsync(args).Result;
|
2022-07-18 16:57:27 +00:00
|
|
|
}
|
2022-07-28 11:46:54 +00:00
|
|
|
internal static async Task SignXml(string xmlFilename, string keyFilename)
|
2022-07-18 16:57:27 +00:00
|
|
|
{
|
2022-07-28 11:46:54 +00:00
|
|
|
if (String.IsNullOrWhiteSpace(xmlFilename))
|
|
|
|
throw new ArgumentException(nameof(xmlFilename));
|
|
|
|
if (String.IsNullOrWhiteSpace(keyFilename))
|
|
|
|
throw new ArgumentException(nameof(keyFilename));
|
2022-07-18 16:57:27 +00:00
|
|
|
|
|
|
|
Console.Write("Signing xml...");
|
|
|
|
|
2022-07-28 11:46:54 +00:00
|
|
|
// 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
|
2022-08-11 15:59:46 +00:00
|
|
|
string privateKeyText = File.ReadAllText(keyFilename);
|
2022-07-28 11:46:54 +00:00
|
|
|
var privateKey = RSA.Create();
|
|
|
|
privateKey.ImportFromPem(privateKeyText);
|
2022-07-18 16:57:27 +00:00
|
|
|
|
|
|
|
// Add the key to the SignedXml document.
|
2022-07-28 11:46:54 +00:00
|
|
|
signedXml.SigningKey = privateKey;
|
2022-07-18 16:57:27 +00:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
2022-07-19 22:06:57 +00:00
|
|
|
// Add keyinfo block
|
|
|
|
KeyInfo keyInfo = new KeyInfo();
|
2022-07-28 11:46:54 +00:00
|
|
|
keyInfo.AddClause(new RSAKeyValue((RSA)privateKey));
|
2022-07-19 22:06:57 +00:00
|
|
|
signedXml.KeyInfo = keyInfo;
|
|
|
|
|
2022-07-18 16:57:27 +00:00
|
|
|
// 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.
|
2022-07-28 11:46:54 +00:00
|
|
|
unsignedDoc.DocumentElement.AppendChild(unsignedDoc.ImportNode(xmlDigitalSignature, true));
|
2022-07-29 00:48:29 +00:00
|
|
|
string signedFilename = "signed_" + xmlFilename;
|
|
|
|
unsignedDoc.Save(signedFilename);
|
|
|
|
Console.WriteLine("Xml signed and written to " + signedFilename);
|
2022-07-18 16:57:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Verify the signature of an XML file against an asymmetric
|
|
|
|
// algorithm and return the result.
|
2022-07-28 11:46:54 +00:00
|
|
|
internal static async Task ValidateXml(string signedFilename, string certFilename)
|
2022-07-18 16:57:27 +00:00
|
|
|
{
|
|
|
|
// Check arguments.
|
2022-07-28 11:46:54 +00:00
|
|
|
if (String.IsNullOrWhiteSpace(signedFilename))
|
|
|
|
throw new ArgumentException(nameof(signedFilename));
|
|
|
|
if (certFilename == null)
|
|
|
|
throw new ArgumentException(nameof(certFilename));
|
2022-07-18 16:57:27 +00:00
|
|
|
|
|
|
|
Console.Write("Verifying signature...");
|
|
|
|
// Create a new SignedXml object and pass it
|
|
|
|
// the XML document class.
|
2022-07-28 11:46:54 +00:00
|
|
|
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();
|
2022-07-18 16:57:27 +00:00
|
|
|
|
|
|
|
// Find the "Signature" node and create a new
|
|
|
|
// XmlNodeList object.
|
2022-07-28 11:46:54 +00:00
|
|
|
XmlNodeList nodeList = signedDoc.GetElementsByTagName("Signature");
|
2022-07-18 16:57:27 +00:00
|
|
|
|
|
|
|
// 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 <signature> node.
|
|
|
|
signedXml.LoadXml((XmlElement)nodeList[0]);
|
|
|
|
Boolean isValid = false;
|
|
|
|
try
|
|
|
|
{
|
2022-07-28 11:46:54 +00:00
|
|
|
isValid = signedXml.CheckSignature(publicKey);
|
2022-07-19 22:06:57 +00:00
|
|
|
}
|
|
|
|
catch (Exception e)
|
2022-07-18 16:57:27 +00:00
|
|
|
{
|
|
|
|
Console.WriteLine(e.Message);
|
|
|
|
}
|
2022-07-19 22:06:57 +00:00
|
|
|
|
2022-07-18 16:57:27 +00:00
|
|
|
// Check the signature and return the result.
|
2022-07-28 11:46:54 +00:00
|
|
|
if (isValid)
|
|
|
|
{
|
|
|
|
Console.WriteLine("Signature is valid!");
|
2022-07-29 00:48:29 +00:00
|
|
|
}
|
|
|
|
else
|
2022-07-28 11:46:54 +00:00
|
|
|
{
|
|
|
|
Console.WriteLine("Signature is not valid.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-11 15:59:46 +00:00
|
|
|
internal static async Task DebugRim(string filename, string keyFilename)
|
2022-07-28 11:46:54 +00:00
|
|
|
{
|
|
|
|
if (String.IsNullOrWhiteSpace(filename))
|
|
|
|
{
|
|
|
|
throw new ArgumentException(nameof(filename));
|
2022-08-11 15:59:46 +00:00
|
|
|
} else if (String.IsNullOrWhiteSpace(keyFilename))
|
|
|
|
{
|
|
|
|
throw new ArgumentException(nameof(keyFilename));
|
2022-07-28 11:46:54 +00:00
|
|
|
}
|
2022-08-11 15:59:46 +00:00
|
|
|
|
|
|
|
XmlDocument xmlToBeSigned = new XmlDocument();
|
2022-07-28 11:46:54 +00:00
|
|
|
XmlDocument xmlDoc = new XmlDocument();
|
|
|
|
xmlDoc.Load(filename);
|
2022-08-11 15:59:46 +00:00
|
|
|
XmlNodeList nodes = xmlDoc.GetElementsByTagName("SoftwareIdentity");
|
|
|
|
XmlNodeList signatureNodes = xmlDoc.GetElementsByTagName("Signature");
|
2022-07-28 11:46:54 +00:00
|
|
|
|
2022-08-11 15:59:46 +00:00
|
|
|
//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));
|
2022-07-18 16:57:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|