I am trying to sign an XML file in C# using Signature Class
library by Microsoft.
What I have done is like this-
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Xml;
using XMLSigner.Model;
using DataObject = System.Security.Cryptography.Xml.DataObject;
internal static XmlDocument GetSignedXMLDocument(XmlDocument xmlDocument, X509Certificate2 certificate, long procedureSerial = -1, string reason = "")
{
//Check if local time is OK
if(!Ntp.CheckIfLocalTimeIsOk()) {
MessageBox.Show("PC Time is need to be updated before sign !");
return null; //Last Sign Not Verified
}
//Then sign the xml
try
{
//MessageBox.Show(certificate.Subject);
SignedXml signedXml = new SignedXml(xmlDocument);
signedXml.SigningKey = certificate.PrivateKey;
// Create a reference to be signed
Reference reference = new Reference();
/////////////////////
reference.Uri = "";//"#" + procedureSerial;
//reference.Type = reason;
//reference.Id = DateTime.UtcNow.Ticks.ToString();
reference.Id = Base64EncodedCurrentTime();
//reference.TransformChain = ;
/////////////////////
// Add an enveloped transformation to the reference.
XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform(true);
reference.AddTransform(env);
// Add the reference to the SignedXml object.
signedXml.AddReference(reference);
//canonicalize
XmlDsigC14NTransform c14t = new XmlDsigC14NTransform();
reference.AddTransform(c14t);
KeyInfo keyInfo = new KeyInfo();
KeyInfoX509Data keyInfoData = new KeyInfoX509Data(certificate);
KeyInfoName kin = new KeyInfoName();
//kin.Value = "Public key of certificate";
kin.Value = certificate.FriendlyName;
RSA rsa = (RSA)certificate.PublicKey.Key;
RSAKeyValue rkv = new RSAKeyValue(rsa);
keyInfo.AddClause(rkv);
keyInfo.AddClause(kin);
keyInfo.AddClause(keyInfoData);
signedXml.KeyInfo = keyInfo;
//////////////////////////////////////////Add Other Data as we need////
// Add the data object to the signature.
//CreateMetaDataObject("Name", GetNetworkTime());
signedXml.AddObject(CreateMetaDataObject(procedureSerial, reason));
///////////////////////////////////////////////////////////////////////
// Compute the signature.
signedXml.ComputeSignature();
// Get the XML representation of the signature and save
// it to an XmlElement object.
XmlElement xmlDigitalSignature = signedXml.GetXml();
xmlDocument.DocumentElement.AppendChild(
xmlDocument.ImportNode(xmlDigitalSignature, true)
);
/////////////////////
} catch (Exception exception) {
MessageBox.Show("Internal System Error during sign");
throw exception;
}
return xmlDocument;
}
And it is working completely fine. But I have an issue with this code. I have to use TSA Server
for the stored time in the XML Signature
, but the time is set from local PC, to avoid this issue, I have checked time manually from Ntp.CheckIfLocalTimeIsOk()
function defined in here. But I like to have the time come from a TSA link like-
Is it possible to configure TSA
in XmlSignature
in C#?
XML data can be represented in different ways, so this step is vital to verification. Add the Reference object to the SignedXml object. Compute the signature by calling the ComputeSignature method. Retrieve the XML representation of the signature (a < Signature > element) and save it to a new XmlElement object.
Add the Reference object to the SignedXml object. Compute the signature by calling the ComputeSignature method. Retrieve the XML representation of the signature (a < Signature > element) and save it to a new XmlElement object. Append the element to the XmlDocument object. Save the document.
To digitally sign an XML document. Create a CspParameters object and specify the name of the key container. C#. CspParameters cspParams = new CspParameters (); cspParams.KeyContainerName = "XML_DSIG_RSA_KEY"; Dim cspParams As New CspParameters () cspParams.KeyContainerName = "XML_DSIG_RSA_KEY". Generate an asymmetric key using ...
The key is automatically saved to the key container when you pass the CspParameters object to the constructor of the RSACryptoServiceProvider class. This key will be used to sign the XML document.
I have solved the problem myself.
What I have done is to create a hash from the XMLDocument
like this-
private static byte[] GetXmlHashByteStream(XmlDocument xmlDoc)
{
byte[] hash;
XmlDsigC14NTransform transformer = new XmlDsigC14NTransform();
transformer.LoadInput(xmlDoc);
using (Stream stream = (Stream)transformer.GetOutput(typeof(Stream)))
{
SHA1 sha1 = SHA1.Create();
hash = sha1.ComputeHash(stream);
stream.Close();
}
return hash;
}
Then get the Timestamp Hash like this-
string stampURI = "http://timestamp.globalsign.com/scripts/timstamp.dll"
private TimeStampResponse GetSignedHashFromTsa(byte[] hash)
{
TimeStampRequestGenerator reqGen = new TimeStampRequestGenerator();
TimeStampRequest request = reqGen.Generate(
TspAlgorithms.Sha1,
hash,
BigInteger.ValueOf(100)
);
byte[] reqData = request.GetEncoded();
HttpWebRequest httpReq = (HttpWebRequest)WebRequest.Create(stampURI);
httpReq.Method = "POST";
httpReq.ContentType = "application/timestamp-query";
httpReq.ContentLength = reqData.Length;
//Configure Timeout
//httpReq.Timeout = 5000;
//httpReq.ReadWriteTimeout = 32000;
// Write the request content
Stream reqStream = httpReq.GetRequestStream();
reqStream.Write(reqData, 0, reqData.Length);
reqStream.Close();
HttpWebResponse httpResp = (HttpWebResponse)httpReq.GetResponse();
// Read the response
Stream respStream = new BufferedStream(httpResp.GetResponseStream());
TimeStampResponse response = new TimeStampResponse(respStream);
respStream.Close();
return response;
}
If you like to get signed Timestamp
string from the response, you can do like this-
internal string GetSignedHashFromTsa(XmlDocument xmlDxocument)
{
byte[] hash = GetXmlHashByteStream(xmlDxocument);
TimeStampResponse timeStampResponse = GetSignedHashFromTsa(hash);
byte[] signedEncodedByteStream = timeStampResponse.GetEncoded();
return Convert.ToBase64String(signedEncodedByteStream);
}
If you like to get the time from the hash string, then you have to do something like this-
internal static DateTime? GetTsaTimeFromSignedHash(string tsaSignedHashString)
{
try {
byte[] bytes = Convert.FromBase64String(tsaSignedHashString);
TimeStampResponse timeStampResponse = new TimeStampResponse(bytes);
return timeStampResponse.TimeStampToken.TimeStampInfo.GenTime;
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
//throw ex;
return null;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With