.NET Signed XML Prefix
Asked Answered
C

3

11

Is there a way to set the prefix on the Signature of a Signed XML Document (SignedXml class in .Net)?

So instead of:

<Signature xmlns="http://www.w3.org/2000/09/xmldsig#>
...
</Signature>

I could have the following:

<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#>
...
</ds:Signature>
Classic answered 19/12, 2008 at 16:54 Comment(0)
L
7

First of all, there really isn't any good reason to do this. The two forms are functionally equivalent. Any well-behaved XML processor will handle them absolutely identically. So unless you are trying to talk to an application that doesn't properly implement XML namespaces, it's better (IMO) just to leave the default form alone. (And even in that case, it would be better, if at all possible, to get the faulty application fixed instead.)

That said, you can manually set the prefix on the XmlElement that is returned by SignedXml.GetXml() and its child elements using XPath like this:

XmlElement signature = signedXml.GetXml();
foreach (XmlNode node in signature.SelectNodes(
    "descendant-or-self::*[namespace-uri()='http://www.w3.org/2000/09/xmldsig#']"))
{
    node.Prefix = "ds";
}
Limestone answered 19/12, 2008 at 20:41 Comment(4)
I agree that it is the same, and that it should work. However, I'm not sure if I'm going to be able to get the other party to work w/it not having the ds prefix. Won't the simple act of changing the prefix after sig in generated cause it to fail validation?Classic
It could break the signature, depending on whether or not the Signature element was included in the information being signed (if the Transform doesn't exclude it, it will break). So this wouldn't work in all possible cases.Limestone
If you look at the SignedXml class in Reflector, it seems to be pretty much hardwired to NOT use a prefix, so unless you can get it to exclude the Signature element from the signature I'm not sure there's any other workable options...Limestone
For someone finding this thread nowadays, I'd recommend dotPeek from jetbrains now over red gate's product. In 2008, reflector was the best, for sure. Now, I definitely think dotPeek is better.Uthrop
E
9

It can't be done. If you modify the XML after it has been signed it may not be able to be verified, which was the case in the example above. IMO this is a flaw in MSFT's digital signature implementation that you will have to live with.

A lot of people will say that there is no reason to do this, and they are technically correct. But when you are dealing with a huge vendor (i.e. a state government or bank), good luck getting them to change it on their end. Most reference implementations include it.

UPDATE: The signature signs everything in the SignedInfo element, so if you go updating that element after the fact, then the Signature is no longer valid. You have "tampered" with the message.

Eonian answered 3/4, 2009 at 21:13 Comment(1)
any solution on this, to how we set prefix and verify it?Interjection
L
7

First of all, there really isn't any good reason to do this. The two forms are functionally equivalent. Any well-behaved XML processor will handle them absolutely identically. So unless you are trying to talk to an application that doesn't properly implement XML namespaces, it's better (IMO) just to leave the default form alone. (And even in that case, it would be better, if at all possible, to get the faulty application fixed instead.)

That said, you can manually set the prefix on the XmlElement that is returned by SignedXml.GetXml() and its child elements using XPath like this:

XmlElement signature = signedXml.GetXml();
foreach (XmlNode node in signature.SelectNodes(
    "descendant-or-self::*[namespace-uri()='http://www.w3.org/2000/09/xmldsig#']"))
{
    node.Prefix = "ds";
}
Limestone answered 19/12, 2008 at 20:41 Comment(4)
I agree that it is the same, and that it should work. However, I'm not sure if I'm going to be able to get the other party to work w/it not having the ds prefix. Won't the simple act of changing the prefix after sig in generated cause it to fail validation?Classic
It could break the signature, depending on whether or not the Signature element was included in the information being signed (if the Transform doesn't exclude it, it will break). So this wouldn't work in all possible cases.Limestone
If you look at the SignedXml class in Reflector, it seems to be pretty much hardwired to NOT use a prefix, so unless you can get it to exclude the Signature element from the signature I'm not sure there's any other workable options...Limestone
For someone finding this thread nowadays, I'd recommend dotPeek from jetbrains now over red gate's product. In 2008, reflector was the best, for sure. Now, I definitely think dotPeek is better.Uthrop
E
1

It can be done, but it's necessary to modify the SignedXml class to add the prefix before getting the digest value of the SignedInfo node.

The ComputeSignature method will be modify to add the prefix parameter

public void ComputeSignature(string prefix){...}

When this method is called it calculates the Signature Value by digesting the value of the SignedInfo node, if you get this value without the "ds" prefix and then add the prefix you will get an invalid signature, so you will have to add the prefix BEFORE getting the digest value of the signedinfo node.

This digest value is generated in the method GetC14NDigest, so this method will be modify to add the prefix parameter and to add the prefix BEFORE getting the digest value

private byte[] GetC14NDigest(HashAlgorithm hash, string prefix)
{
    XmlDocument document = new XmlDocument();
    document.PreserveWhitespace = false;
    XmlElement e = this.SignedInfo.GetXml(); //get the signedinfo nodes
    document.AppendChild(document.ImportNode(e, true));        
    Transform canonicalizationMethodObject = this.SignedInfo.CanonicalizationMethodObject;       
    SetPrefix(prefix, document.DocumentElement); /*Set the prefix before getting the HASH*/
    canonicalizationMethodObject.LoadInput(document);
    return canonicalizationMethodObject.GetDigestedOutput(hash);
}

Ok, so now you have the Signature Value of the SignedInfo nodes WITH the "ds" prefix, that being said you still DON'T have the xml with the prefix yet, so if you just call the GetXml method you will get the xml without the prefix and of course because the signature value was calculated considering the ds prefix you will have an invalid signature. To avoid that and get the xml structure with the prefix you have to modify the GetXml method, add the prefix parameter, and call the SetPrefix method wich will add the "ds" prefix to all the nodes in the Signature Xml

public XmlElement GetXml(string prefix)
{
    XmlElement e = this.GetXml();
    SetPrefix(prefix, e); //return the xml structure with the prefix
    return e;
}

I'll leave here the class with those modifications

CUSTOM CLASS

internal sealed class CustomSignedXml : SignedXml
{
    XmlElement obj = null;
    public CustomSignedXml (XmlDocument xml)
        : base(xml)
    {
    }

    public CustomSignedXml (XmlElement xmlElement)
        : base(xmlElement)
    {

    }

    public XmlElement GetXml(string prefix)
    {
        XmlElement e = this.GetXml();
        SetPrefix(prefix, e);
        return e;
    }

    public void ComputeSignature(string prefix)
    {
        this.BuildDigestedReferences();
        AsymmetricAlgorithm signingKey = this.SigningKey;
        if (signingKey == null)
        {
            throw new CryptographicException("Cryptography_Xml_LoadKeyFailed");
        }
        if (this.SignedInfo.SignatureMethod == null)
        {
            if (!(signingKey is DSA))
            {
                if (!(signingKey is RSA))
                {
                    throw new CryptographicException("Cryptography_Xml_CreatedKeyFailed");
                }
                if (this.SignedInfo.SignatureMethod == null)
                {
                    this.SignedInfo.SignatureMethod = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
                }
            }
            else
            {
                this.SignedInfo.SignatureMethod = "http://www.w3.org/2000/09/xmldsig#dsa-sha1";
            }
        }
        SignatureDescription description = CryptoConfig.CreateFromName(this.SignedInfo.SignatureMethod) as SignatureDescription;
        if (description == null)
        {
            throw new CryptographicException("Cryptography_Xml_SignatureDescriptionNotCreated");
        }
        HashAlgorithm hash = description.CreateDigest();
        if (hash == null)
        {
            throw new CryptographicException("Cryptography_Xml_CreateHashAlgorithmFailed");
        }
        this.GetC14NDigest(hash, prefix);
        this.m_signature.SignatureValue = description.CreateFormatter(signingKey).CreateSignature(hash);
    }         

    private byte[] GetC14NDigest(HashAlgorithm hash, string prefix)
    {

        XmlDocument document = new XmlDocument();
        document.PreserveWhitespace = false;
        XmlElement e = this.SignedInfo.GetXml();
        document.AppendChild(document.ImportNode(e, true));               

        Transform canonicalizationMethodObject = this.SignedInfo.CanonicalizationMethodObject;            
        SetPrefix(prefix, document.DocumentElement); //Set the prefix before getting the HASH
        canonicalizationMethodObject.LoadInput(document);
        return canonicalizationMethodObject.GetDigestedOutput(hash);
    }

    private void BuildDigestedReferences()
    {
        Type t = typeof(SignedXml);
        MethodInfo m = t.GetMethod("BuildDigestedReferences", BindingFlags.NonPublic | BindingFlags.Instance);
        m.Invoke(this, new object[] { });
    }

    private void SetPrefix(string prefix, XmlNode node)
    {
       foreach (XmlNode n in node.ChildNodes)
          SetPrefix(prefix, n);
       node.Prefix = prefix;
    }
}

And the way to use it

CustomSignedXml signedXml = new CustomSignedXml();
.
.//your code
. 

//compute the signature with the "ds" prefix

signedXml.ComputeSignature("ds");

//get the xml of the signature with the "ds" prefix

XmlElement xmlDigitalSignature = signedXml.GetXml("ds");
Effable answered 8/6, 2017 at 16:57 Comment(1)
This code doesn't work because it calls this.SignedInfo.GetXml(); in GetC14NDigest which doesn't have the needed prefix in the XML elements generated.Beltz

© 2022 - 2024 — McMap. All rights reserved.