views:

769

answers:

4

I am trying to sign an XML file in C# .NET 3.5 with a private RSA Key generated by OpenSSL.

Here is how I proceeded: I converted the RSA key from PEM format to XML format using the chilkat framework (www.example-code.com/csharp/cert_usePrivateKeyFromPEM.asp)

With my XML key, I am now able to use native .NET Functions, which I prefer. So I used the methods described on MSDN.

So, in the end, my source code looks like this:

RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider();

//Load the private key from xml file
XmlDocument xmlPrivateKey = new XmlDocument();
xmlPrivateKey.Load("PrivateKey.xml");
rsaProvider.FromXmlString(xmlPrivateKey.InnerXml);

 // Create a SignedXml object.
 SignedXml signedXml = new SignedXml(Doc);

 // Add the key to the SignedXml document.
 signedXml.SigningKey = Key;

 // 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);

 // 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.
 Doc.DocumentElement.AppendChild(Doc.ImportNode(xmlDigitalSignature, true));

The Signed XML I get with this function looks OK, I have the XML element at the end of the file, like it is supposed to be:

<Signature xmlns="http://www.w3.org/2000/09/xmldsig#"&gt;
<SignedInfo>
  <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
  <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
  <Reference URI="">
    <Transforms>
      <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
    </Transforms>
    <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
    <DigestValue>qoGPSbe4oR9e2XKN6MzP+7XlXYI=</DigestValue>
  </Reference>
</SignedInfo>
<SignatureValue>iPQ6IET400CXfchWJcP22p2gK6RpEc9mkSgfoA94fL5UM6+AB5+IO6BbjsNt31q6MB8hR6lAIcnjzHzc5SeXvFP8Py2bqHTYJvcSA6KcKCQl1LiDNt12UwWiKpSkus2p0LdAeeZJNy9aDxjC/blUaZEr4uPFt0kGCD7h1NQM2SY=</SignatureValue>

The problem is that when I try to verify the signature using xmlsec at this URL: http://www.aleksey.com/xmlsec/xmldsig-verifier.html. I get a message telling me the signature is invalid.

I have been looking for the error in my code for days and I can't find out. I am beginning to think that the conversion from PEM to XML file might be the problem but I don't know how to test this. Moreover, I did not find any other way to convert to key or to use directly the PEM file in .NET.

Did anyone manage to get a valid signature in .NET?

A: 

Yes, I have managed to do that. I think the problem is with your reference. The uri should point to the id of the element that the signature is for. Anyway, check the below code, hope it points you in the right direction.

/Klaus

/// <summary>
    /// Signs an XmlDocument with an xml signature using the signing certificate given as argument to the method.
    /// </summary>
    /// <param name="doc">The XmlDocument to be signed</param>
    /// <param name="id">The is of the topmost element in the xmldocument</param>
    /// <param name="cert">The certificate used to sign the document</param>
    public static void SignDocument(XmlDocument doc, string id, X509Certificate2 cert)
    {
        SignedXml signedXml = new SignedXml(doc);
        signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
        signedXml.SigningKey = cert.PrivateKey;

        // Retrieve the value of the "ID" attribute on the root assertion element.
        Reference reference = new Reference("#" + id);

        reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
        reference.AddTransform(new XmlDsigExcC14NTransform());

        signedXml.AddReference(reference);

        // Include the public key of the certificate in the assertion.
        signedXml.KeyInfo = new KeyInfo();
        signedXml.KeyInfo.AddClause(new KeyInfoX509Data(cert, X509IncludeOption.WholeChain));

        signedXml.ComputeSignature();
        // Append the computed signature. The signature must be placed as the sibling of the Issuer element.
        XmlNodeList nodes = doc.DocumentElement.GetElementsByTagName("Issuer", Saml20Constants.ASSERTION);
        // doc.DocumentElement.InsertAfter(doc.ImportNode(signedXml.GetXml(), true), nodes[0]);            
        nodes[0].ParentNode.InsertAfter(doc.ImportNode(signedXml.GetXml(), true), nodes[0]);
    }
klausbyskov
With the "" you implicit signs all of the XML if you use an id you only sign parts of it.
Henrik Jepsen
The signature I have to do must follow the rule "Only what is seen should be signed". I assumed I had to sign the whole document, but I may was wrong... I will try so change the Uri of the reference. Hope fully it will do the trick.
Nicolas Riou
How do you set the Id of the top element ? I tried this : // Create a data object to hold the data to sign. DataObject dataObject = new DataObject(); dataObject.Data = Doc.ChildNodes; dataObject.Id = "MyObjectId"; // Add the data object to the signature. signedXml.AddObject(dataObject); // Add the key to the SignedXml document. signedXml.SigningKey = Key; // Create a reference to be signed. Reference reference = new Reference("#MyObjectId");But i am getting an exception ...
Nicolas Riou
A: 

It seems to me, that you sign the everrything in the xml:reference.Uri = "";

Purhaps you break the signature when you insert it in the document or insert it in a wrong way.

Also be aware of namespaces and whitespace some of it is also signed which can cause problems later on when working on the signed document.

Henrik Jepsen
A: 

So, next step of my personal cryptographic nightmare :

I am now trying to change the Uri of the Reference, so that I don't sign the whole xml document but only the top Element. The source code now looks like this :

private static void SignXml(XmlDocument Doc, RSA Key)
{
        // Check arguments.
        if (Doc == null)
            throw new ArgumentException("Le xml est null");
        if (Key == null)
            throw new ArgumentException("La cle est requise");

        // Create a SignedXml object.
        SignedXml signedXml = new SignedXml();

        // Add the key to the SignedXml document.
        signedXml.SigningKey = Key;

        // Create a data object to hold the data to sign.
        DataObject dataObject = new DataObject();
        dataObject.Data = Doc.ChildNodes;
        dataObject.Id = "MyObjectId";

        // Add the data object to the signature.
        signedXml.AddObject(dataObject);

        // Create a reference to be signed.
        Reference reference = new Reference();
        reference.Uri = "#MyObjectId";

        // Add an enveloped transformation to the reference.
        reference.AddTransform( new XmlDsigEnvelopedSignatureTransform());

        // Add the reference to the SignedXml object.
        signedXml.AddReference(reference);

        // 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.
        Doc.DocumentElement.AppendChild(Doc.ImportNode(xmlDigitalSignature, true));


    }

The line signedXml.ComputeSignature(); throws an exception : System.InvalidOperationException : "Le noeud spécifié ne peut pas être inséré comme enfant valide de ce noeud, car le noeud spécifié n'est pas du type correct." (Sorry about french :) )

In English, that sould be something like that : Specified node can not be inserted as a valid child of this node, because the specified node is not of the correct type.

Nicolas Riou
A: 

Try to replace

dataObject.Data = Doc.ChildNodes;

with this:

dataObject.Data = Doc.GetElementsByTagName("YourRootNodeNameHere");
henningc