Signed document exchange InfoPath <--> .Net 2.0 - InfoPath Dev
in

InfoPath Dev

Signed document exchange InfoPath <--> .Net 2.0

Last post 03-16-2009 07:47 AM by bostjan. 6 replies.
Page 1 of 1 (7 items)
Sort Posts: Previous Next
  • 08-23-2005 07:18 AM

    • bostjan
    • Not Ranked
    • Joined on 08-23-2005
    • Slovenia
    • Posts 5

    Signed document exchange InfoPath <--> .Net 2.0

    Hi,

    I am trying to exchange signed XML documents using E-Mail and use InfoPath to make it possible for the user to edit document content. On the server side I would like to use the .NET 2.0 (at the moment Beta 2) to create and process the documents (that means System.Xml).

    At the moment I can neither sign the simplest xml document, so that InfoPath would be able to verify the signature, nor verify the the signature using System.Xml.

    The code I use for signign looks following (comments removed for brevity :))

    public static void SignXmlFile(string FileName, string SignedFileName)
    {
    XmlDocument doc = new XmlDocument();
    doc.PreserveWhitespace = false;
    doc.Load(new XmlTextReader(FileName));

    SignedXml signedXml = new SignedXml(doc);
    Reference reference = new Reference();
    reference.Uri = "";
    Transform trns = new XmlDsigC14NTransform();
    reference.AddTransform(trns);
    XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
    reference.AddTransform(env);
    signedXml.AddReference(reference);

    X509Store store = new X509Store(StoreLocation.CurrentUser);
    store.Open(OpenFlags.ReadOnly);
    X509Certificate2Collection certificates = store.Certificates.Find(X509FindType.FindByIssuerName, "test", false);
    X509Certificate2 certificate = certificates[0];

    KeyInfo keyInfo = new KeyInfo();
    keyInfo.AddClause(new KeyInfoX509Data(certificate));
    signedXml.KeyInfo = keyInfo;
    signedXml.SigningKey = certificate.PrivateKey;

    signedXml.Signature.Id = "MySignature_group1_000";
    signedXml.ComputeSignature();

    XmlElement xmlDigitalSignature = signedXml.GetXml();
    XmlNode signatures = doc.SelectSingleNode("//*[local-name(.) = 'signatures2']");
    if (signatures == null)
    signatures = doc.DocumentElement;
    signatures.AppendChild(doc.ImportNode(xmlDigitalSignature, true));
    if (doc.FirstChild is XmlDeclaration)
    {
    doc.RemoveChild(doc.FirstChild);
    }

    // Save the signed XML document to a file specified
    // using the passed string.
    XmlTextWriter xmltw = new XmlTextWriter(SignedFileName, new UTF8Encoding(false));
    doc.WriteTo(xmltw);
    xmltw.Close();
    store.Close();
    }


    The code I use to verify the content, which verifies the signatures I create using above code, but not the InfoPath signatures looks following:

    public static Boolean VerifyXmlFile(String Name)
    {
    XmlDocument xmlDocument = new XmlDocument();
    xmlDocument.PreserveWhitespace = true;
    xmlDocument.Load(Name);

    SignedXml signedXml = new SignedXml(xmlDocument);
    XmlNodeList nodeList = xmlDocument.GetElementsByTagName("Signature");

    bool retVal = true;
    foreach (XmlElement e in nodeList)
    {
    signedXml.LoadXml((e));
    XmlNodeList certificates = e.GetElementsByTagName("X509Certificate");
    X509Certificate2 cert = CreateWSEX509CertificateFromXMLElement((XmlElement)certificates[0]);
    RSACryptoServiceProvider rsacsp = (RSACryptoServiceProvider)cert.PublicKey.Key;
    retVal = retVal && signedXml.CheckSignature(rsacsp);
    }
    return retVal;
    }


    I have seen a post warning from using xml:lang and xml:base attributes, because of differences in System.Xml and MSXML. I have included the code in InfoPath form, which removes all the xml: attributes:

    function XDocument::OnLoad(eventObj)
    {
    try
    {
    xmlAttributes = XDocument.DOM.selectNodes("//@*[namespace-uri(.)='http://www.w3.org/XML/1998/namespace']");
    if (xmlAttributes.Count > 0)
    xmlAttributes.removeAll();
    }
    catch(allofthem) {};
    }


    Does anybody have any idea, what am I doing wrong here or what am I missing, or should I attribute this to Beta status or to something else?

    Best regards,

    Boštjan
  • 09-02-2005 08:25 AM In reply to

    • bostjan
    • Not Ranked
    • Joined on 08-23-2005
    • Slovenia
    • Posts 5

    Re: Signed document exchange InfoPath <--> .Net 2.0

    Hmm, I find it hard to believe, that noone exchanges signed data with InfoPath.
    How do you create digital signatures on the server side, so that InfoPath is able to verify them?

    Best regards,

    Boštjan
  • 09-02-2005 02:50 PM In reply to

    Re: Signed document exchange InfoPath <--> .Net 2.0

    Hi Boštjan and welcome to the board,
    I'm terribly sorry about missing your post. We typically sign XML data using the built-in InfoPath support which does not require all of the code you have above. My guess is that it's a difference between System.XML (.NET 2.0 version) and MSXML4 (what InfoPath uses). This can be tricky to debug. If you'd like to have us do some consulting for you, send me a private e-mail.Otherwise, good luck exploring the issue. You should also try using the "stock" digital signature support in InfoPath. It's easy to use. Cheers!

    Patrick Halstead [InfoPath MVP]
    InfoPathDev
    Patrick Halstead
    Project Manager at Qdabra
  • 09-06-2005 07:24 AM In reply to

    • bostjan
    • Not Ranked
    • Joined on 08-23-2005
    • Slovenia
    • Posts 5

    Re: Signed document exchange InfoPath <--> .Net 2.0

    Hello Patrick,

    Ok, I will try to approximately describe a scenario: We would like to plan centrally the activities of field personell. For this purpose we would like to generate requests containing following information:
    on specific date go visit Customer X, perform listed tasks and when you are finished, please fill out this form and send it back via e-mail. Engagement planning and report processing should be automated.

    For several reasons like security, server load, ..., we would not like to install Office on servers, where the requests will be generated or reports will be processed. For this reason we would like to create and verify InfoPath signatures using .NET System.Xml. MSXML would be ok, if for some reason we cannot solve all problems with System.Xml.

    The information sent via in mail attachments is not that sensitive, that signatures would be absolutely neccessary. But when we come to processing the reports in the incoming mail, I would really like to have some safeguards, that would prevent an outsider from disrupting this information-system operation just by sending an e-mail containing incorrect and malicious data.

    For this reason I would like to create signatures verifiable by InfoPath and verify signatures created by InfoPath on the server, without the Office binaries.

    Best regards,

    Boštjan Peterca
  • 09-07-2005 01:15 AM In reply to

    Re: Signed document exchange InfoPath <--> .Net 2.0

    Hi again,
    Why not just use the InfoPath digital signatures? You can do this. It will take some work, but we have done exactly this scenario before. Let us know if you would like us to help you with your solution! :)

    Patrick Halstead [InfoPath MVP]
    InfoPathDev
    Patrick Halstead
    Project Manager at Qdabra
  • 10-04-2005 04:39 AM In reply to

    • bostjan
    • Not Ranked
    • Joined on 08-23-2005
    • Slovenia
    • Posts 5

    Re: Signed document exchange InfoPath <--> .Net 2.0

    Hello,

    After some experimenting and debugging I have managed to creata a signature using .NET 2.0 classes, that was accepted by InfoPath.

    The way InfoPath treats signed XML is a black box to me, and I could not find a way to get additional information out of InfoPath, why signature status is error. Thus was my approach to try and create an identical signature to the one InfoPath itself creates, when a xml document is signed by InfoPath.

    On the way I had to solve the following problems:
    1. SignedXml does not allow Reference-s to XML inside of DataObjects.
    On Shawn Farkas suggestion I have subclassed the SignedXml class and overriden the GetIdElement method, to search for Reference targets also inside of the DataObjects.
    2. When adding DataObject-s containing for example SignatureProperties, special care is needed with namespaces.
    When computing digests, all the namespaces from the parent elements in the document are copied to the referenced element, before digest is calculated. This may cause the Signature, to which a DataObject has been added not to verify even with the .NET 2.0 classes. So mind the namespace declarations.

    If anyone is interested I can post the code I used to create a test signature & code to subclass the SignedXml.

    Boštjan
  • 03-16-2009 07:47 AM In reply to

    • bostjan
    • Not Ranked
    • Joined on 08-23-2005
    • Slovenia
    • Posts 5

    Re: Signed document exchange InfoPath <--> .Net 2.0

    Recieved a question per Mail about my problem today, so I have posted parts of my experimental code here: http://closetnutsnbolts.wordpress.com/2009/03/16/infopathsigning/

    Years ago I've been looking for a solution for creating signed XML documents, which could be used and their signatures verified using Microsoft Office Infopath. And of course vice versa, to verify the InfoPath signatures using the .NET XML and cryprography services. Today I've been asked about my findings, so I'm posting my experiments here, perhaps it could save someone some searching and sleepless nights.

    Happy experimenting!

    Now to the code. First problem is, that SignedXml class did not find the signature in the InfoPath document. We needed to extend the SignedXml, overriding the GetIdElement function. Like this:

    public class KdsSignedXml : SignedXml
    {
        public KdsSignedXml() : base() { }
        public KdsSignedXml(XmlDocument document) : base(document) { }
        public KdsSignedXml(XmlElement elem) : base(elem) { }
         ///
        /// Searches for element using id in idValue and extended the search into DataObjects added to SignedXml class
        ///
        ///  document to search
        /// id to search for in document and in data objects
        /// XmlElement if found, null if the element with this id is not found
        public override XmlElement GetIdElement(XmlDocument document, string idValue)
        {
            XmlElement retVal = base.GetIdElement(document, idValue);
            if (retVal != null)
                return retVal;
            foreach (DataObject containedDocument in m_signature.ObjectList)
            {
                retVal = GetIdElement(containedDocument.Data, idValue);
                if (retVal != null)
                    return retVal;
            }

            return null;
        }

        private XmlElement GetIdElement(XmlNodeList data, string idValue)
        {
            if (data == null)
                 return null;
            foreach (XmlNode node in data)
            {
                XmlElement element1 = GetIdElement(node, idValue);
                if (element1 != null)
                    return element1;
             }

            return null;
        }

        private XmlElement GetIdElement(XmlNode node, string idValue)
        {
            if (node == null)
                return null;
            XmlElement element1 = node.SelectSingleNode("//*[@Id=\"" + idValue + "\"]") as XmlElement;
            if (element1 != null)
            {
                return element1;
            }
            element1 = node.SelectSingleNode("//*[@id=\"" + idValue + "\"]") as XmlElement;
            if (element1 != null)
            {
                return element1;
            }
            return (node.SelectSingleNode("//*[@ID=\"" + idValue + "\"]") as XmlElement);
        }
    }

    After that it should be easy. I have treated InfoPath as a black box, just trying to produce a document exactly in a form InfoPath itself would produce for my specific schema and my specific case. I did not need to look for a generic solution so I did not even try to understand the InfoPath internals to produce a generic solution. So if you are looking for that kind of recipes, there is still a lot of work ahead, digging through the manifest.xsf.

    In my experimental signing and verifying code, I have accomplished this like following:

    public static void Sign(X509Certificate2 cert, XmlDocument doc, string signatureName, string signatureLocation, XmlElement sigProperties, params string[] references)
    {
        // preserve whitespace
        doc.PreserveWhitespace = true;
    
        // create the signature mgt. object
        KdsSignedXml signedXml = new KdsSignedXml(doc);
    
        // add the references
        if (references.Length == 0)
        {
            AddReference(doc, signedXml, signatureLocation, "");
        }
        else
        {
            foreach (string s in references)
            {
                AddReference(doc, signedXml, signatureLocation, s);
            }
        }
    
        // Add signature properties
        AddNamespaces(signatureLocation, doc, sigProperties);
        signedXml.AddObject(new DataObject("", "", "", sigProperties));
        Reference reference = new Reference("#MyComment_" + signatureName + "_000");
        reference.Type = "http://www.w3.org/2000/09/xmldsig#SignatureProperties";
        signedXml.AddReference(reference);
    
        // sign the document
        KeyInfo keyInfo = new KeyInfo();
        keyInfo.AddClause(new KeyInfoX509Data(cert));
        signedXml.KeyInfo = keyInfo;
        signedXml.SigningKey = cert.PrivateKey;
        signedXml.Signature.Id = "MySignature_" + signatureName + "_000";
        signedXml.ComputeSignature();
    
        // append the signature
        XmlElement xmlDigitalSignature = signedXml.GetXml();
    
        XmlNamespaceManager docNamespaces = new XmlNamespaceManager(doc.NameTable);
        GetNamespaces(docNamespaces, doc.DocumentElement);
    
        XmlNode sigNode = doc.SelectSingleNode(signatureLocation, docNamespaces);
        XmlNode signatures = doc.SelectSingleNode(signatureLocation, docNamespaces);
        if (signatures == null)
            signatures = doc.DocumentElement;
        signatures.AppendChild(doc.ImportNode(xmlDigitalSignature, true));
    }
    
    public static Boolean Verify(XmlDocument doc, string certIssuerName, bool certVerify)
    {
        doc.PreserveWhitespace = true;
    
        KdsSignedXml signedXml = new KdsSignedXml(doc);
    
        // Find the "Signature" node and create a new
        // XmlNodeList object.
        XmlNodeList nodeList = doc.GetElementsByTagName("Signature");
    
        bool retVal = true;
        foreach (XmlElement e in nodeList)
        {
            signedXml.LoadXml((e));
            XmlNodeList certificates =
                e.GetElementsByTagName("X509Certificate");
            X509Certificate2 cert =
                CreateX509CertificateFromXMLElement(
                (XmlElement)certificates[0]);
            if (cert.IssuerName.Name != string.Empty && certIssuerName.CompareTo(cert.IssuerName.Name) != 0)
                return false;
            RSACryptoServiceProvider rsacsp =
                (RSACryptoServiceProvider)cert.PublicKey.Key;
            retVal = retVal && signedXml.CheckSignature(rsacsp);
            if (certVerify)
                retVal = retVal && cert.Verify();
        }
        // Check the signature and return the result.
        return retVal;
    }

    In case you are wondering what some of the utility functions do, here is some experimental and really quick and dirty code used in my experiments.

    private static X509Certificate2
        CreateX509CertificateFromXMLElement(
        System.Xml.XmlElement theCertElement)
    {
    
        // The text of the element should be a Base64 representation of
        // the certificate, so load it as a string.
        string base64CertificateData = theCertElement.InnerText;
    
        // Remove any whitespace that may be cluttering up the data.
        base64CertificateData = base64CertificateData.Replace("\n", "");
        base64CertificateData = base64CertificateData.Replace("\r", "");
        base64CertificateData = base64CertificateData.Replace("\f", "");
        base64CertificateData = base64CertificateData.Replace("\t", "");
        base64CertificateData = base64CertificateData.Replace(" ", "");
    
        // Convert the data to a byte array
        byte[] certificateData = System.Convert.FromBase64String(
           base64CertificateData);
    
        // Create a new X509Certificate from the data.
        X509Certificate2 cert = new X509Certificate2(certificateData);
    
        // Return the result.
        return cert;
    }
    
    
    private static void AddReference(XmlDocument doc, SignedXml sigXml, string sigLoc, string refName)
    {
        Reference reference = new Reference(refName);
        if (refName == String.Empty)
        {
            // Enveloped Signature Transform
            XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
            reference.AddTransform(env);
    
            string dataTransform = "<stylesheet xmlns=\"http://www.w3.org/1999/XSL/Transform\" version=\"1.0\"> <template match=\"/\"><element name=\"SignedData\" namespace=\"http://schemas.microsoft.com/office/infopath/2003/SignatureProperties\"><apply-templates select=\".\" mode=\"skip\"/></element></template><template match=\"@*|node()\" priority=\"100\" mode=\"skip\"><apply-templates select=\"@*|node()\" mode=\"skip\"/></template><template match=\""
                + GetElementXP(GetDocumentRoot(doc))
                + "\" priority=\"200\" mode=\"skip\"><apply-templates select=\".\" mode=\"copy\"/></template><template match=\"@*|node()\" priority=\"100\" mode=\"copy\"><copy><apply-templates select=\"@*|node()\" mode=\"copy\"/></copy></template><template match=\""
                + sigLoc + "/node()\" priority=\"200\" mode=\"copy\" /></stylesheet>";
            Transform trnsip = CreateXsltTransform(dataTransform, doc, sigLoc);
            reference.AddTransform(trnsip);
    
            Transform trns = new XmlDsigC14NTransform();
            reference.AddTransform(trns);
        }
        sigXml.AddReference(reference);
    }
    
    public static void Sign(X509Certificate2 cert, XmlDocument doc, string signatureName, string signatureLocation, string comment, params string[] references)
    {
        string importProperties = "<SignatureProperties xmlns=\"http://www.w3.org/2000/09/xmldsig#\"> <SignatureProperty Id=\"MyComment_"
            + signatureName
            + "_000\" Target=\"#MySignature_Data_000\"> <Comment xmlns=\"http://schemas.microsoft.com/office/infopath/2003/SignatureProperties\">"
            + comment
            + "</Comment></SignatureProperty></SignatureProperties>";
        XmlDocument properties = new XmlDocument();
        properties.PreserveWhitespace = true;
        properties.Load(new System.IO.StringReader(importProperties));
    
        Sign(cert, doc, signatureName, signatureLocation, properties.DocumentElement, references);
    
    }
    

    P.S. I hope wordpress did not eat any of the important " and \ and < and >

Page 1 of 1 (7 items)
Copyright © 2003-2019 Qdabra Software. All rights reserved.
View our Terms of Use.