Search code examples
c#asp.netxmldigital-signaturexml-signature

Configure TSA in Xml Signature in C#


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#?


Solution

  • 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;
    }
    

    Re-

    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;
        }
    }