Merge pull request #488 from nsacyber/c#_xmldsig_validation

[#487] Create xml_dsig_tool
This commit is contained in:
chubtub 2022-09-15 15:12:24 -04:00 committed by GitHub
commit e5da94fa08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 341 additions and 0 deletions

View File

@ -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

View File

@ -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-----

View File

@ -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-----

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<SoftwareIdentity xmlns="http://standards.iso.org/iso/19770/-2/2015/schema.xsd" xmlns:ns2="http://www.w3.org/2000/09/xmldsig#" corpus="false" name="Example.com BIOS" patch="false" supplemental="false" tagId="94f6b457-9ac9-4d35-9b3f-78804173b65as" tagVersion="0" version="01" versionScheme="multipartnumeric" xml:lang="en">
<Entity name="Example Inc" regid="http://Example.com" role="softwareCreator tagCreator"/>
<Link href="https://Example.com/support/ProductA/firmware/installfiles" rel="installationmedia"/>
<Meta xmlns:n8060="http://csrc.nist.gov/ns/swid/2015-extensions/1.0" xmlns:rim="https://trustedcomputinggroup.org/wp-content/uploads/TCG_RIM_Model" n8060:colloquialVersion="Firmware_2019" n8060:edition="12" n8060:product="ProductA" n8060:revision="r2" rim:BindingSpec="PC Client RIM" rim:BindingSpecVersion="1.2" rim:PayloadType="direct" rim:firmwareManufacturerId="00213022" rim:firmwareManufacturerStr="BIOSVendorA" rim:firmwareModel="A0" rim:firmwareVersion="12" rim:pcURIGlobal="https://Example.com/support/ProductA/" rim:pcURILocal="/boot/tcg/manifest/switag/" rim:platformManufacturerId="00201234" rim:platformManufacturerStr="Example.com" rim:platformModel="ProductA" rim:platformVersion="01"/>
<Payload xmlns:rim="https://trustedcomputinggroup.org/wp-content/uploads/TCG_RIM_Model" rim:supportRIMFormat="TCG_EventLog_Assertion" rim:supportRIMURIGlobal="https://Example.com/support/ProductA/firmware/rims/">
<Directory name="rim">
<File xmlns:SHA256="http://www.w3.org/2001/04/xmlenc#sha256" SHA256:hash="4479ca722623f8c47b703996ced3cbd981b06b1ae8a897db70137e0b7c546848" name="Example.com.BIOS.01.rimel" size="7549"/>
</Directory>
</Payload>
</SoftwareIdentity>

View File

@ -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<int> Main(String[] args)
{
var fileOption = new Option<string>(
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<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.")
{
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 <signature> 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));
}
}