How to sign xml with X509 cert, add digest value a

2019-07-26 18:45发布

问题:

I am quite new to C# and especially new to Signing with X509. I have an xml template in which I have to add the certificate used (done) and sign the timestamp (TS-1), the binary security token and the body (id-1). Furthermore, I need to write (e.g. replace placeholders) with the digest value of these 3 elements and add the signature value.

However, I do not really understand the concept, e.g. how to do this. I read a couple of websites, e.g. signing a xml document with x509 certificate but I cannot adapt the code to my problem.

Here is what I tried:

public static string SignXml(string template)
{

    XmlDocument document = new XmlDocument();
    document.LoadXml(template);

        // define elements that will be signed
        XmlNode securityToken = null;
        XmlNode validityPeriod = null;
        XmlNode body = null;
        XmlNode signedInfo = null;
        XmlNode signatureValue = null;
        XmlNodeList digestTags = null;



        XmlNamespaceManager namespaces = new XmlNamespaceManager(document.NameTable);
        namespaces.AddNamespace("ns", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
        namespaces.AddNamespace("nu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
        namespaces.AddNamespace("bo", "http://schemas.xmlsoap.org/soap/envelope/");
        namespaces.AddNamespace("si", "http://www.w3.org/2000/09/xmldsig#");
        namespaces.AddNamespace("sinfo", "soapenv xd xe");

        document.LoadXml(template);
        //XmlNode idNode = document.SelectSingleNode("/My_RootNode/ns:id", namespaces);

        securityToken = document.SelectSingleNode("descendant::ns:BinarySecurityToken", namespaces);
        validityPeriod = document.SelectSingleNode("descendant::nu:Timestamp", namespaces);
        body = document.SelectSingleNode("descendant::bo:Body", namespaces);
        signedInfo = document.SelectSingleNode("descendant::si:SignedInfo", namespaces);
        signatureValue = document.SelectSingleNode("descendant::si::sinfo:SignatureValue", namespaces);
        digestTags = document.SelectNodes("descendant::si:DigestValue", namespaces);

        // add the digests (to know where to write the digests)
        String nodeName = null;
        for (int counter = 0; counter < digestTags.Count; counter++)
        {
            nodeName = digestTags[counter].FirstChild.InnerText;
            if (WebserviceConstants.PLACEHOLDER_AUTHNREQUEST_DIGEST.Equals(nodeName))
            {

                generateDigest(body, digestTags[counter]);
            }
            else if (WebserviceConstants.PLACEHOLDER_CERTIFICATE_DIGEST.Equals(nodeName))
            {

                generateDigest(securityToken, digestTags[counter]);
            }
            else if (WebserviceConstants.PLACEHOLDER_TIMESTAMP_TAG_DIGEST.Equals(nodeName))
            {

                generateDigest(validityPeriod, digestTags[counter]);
            }
        }



        SignedXml signedXml = new SignedXml(document);



    X509Certificate2 cert = new X509Certificate2();
    cert = getbase();

    signedXml.SigningKey = cert.PrivateKey;

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

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

    //canonicalize
    XmlDsigC14NTransform c14t = new XmlDsigC14NTransform();
    reference.AddTransform(c14t);

    KeyInfo keyInfo = new KeyInfo();
    KeyInfoX509Data keyInfoData = new KeyInfoX509Data(cert);
    KeyInfoName kin = new KeyInfoName();
    kin.Value = "Public key of certificate";
    RSACryptoServiceProvider rsaprovider = (RSACryptoServiceProvider)cert.PublicKey.Key;
    RSAKeyValue rkv = new RSAKeyValue(rsaprovider);
    keyInfo.AddClause(kin);
    keyInfo.AddClause(rkv);
    keyInfo.AddClause(keyInfoData);
    signedXml.KeyInfo = keyInfo;

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

    document.DocumentElement.AppendChild(
    document.ImportNode(xmlDigitalSignature, true));
    document.DocumentElement.AppendChild(document.ImportNode(xmlDigitalSignature, true));

        return document.OuterXml;
    }
}

I am questioning myself:

  • How do I get the digest value and how to write it to the corresponding xml node
  • How to calculate the signature value as it "contains" the signed info of all 3 references???

As you can see, I am missing some general background and understanding. Would be really cool if you could help me out!

Thank you

回答1:

You don't have to manually create the nodes of the signature, after you compute the signature you call the GetXml method (you are already doing it: signedXml.GetXml()) and this will return something like this:

<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
  <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" />
            <Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
        </Transforms>
        <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
        <DigestValue>zRSPtja5EtX7hVbyJ11EjoYTRDk=</DigestValue>
    </Reference>
  </SignedInfo>
  <SignatureValue>Ua1/WP28WzfXaxUj....</SignatureValue>
  <KeyInfo>        
    <X509Data>
        <X509Certificate>MIIF3jCCBUegAwIBAgIEPQa1....</X509Certificate>
    </X509Data>
  </KeyInfo>
</Signature>

Then you will only have to replace the whole signature node on your xml template.

--Keeping in mind that the SignedXml will give you that structure now I'll answer your questions

Your first question is it about the digest value of your references? If so, when you call the ComputeSignature method it will calculate it and add it to the corresponding xml node.

The signature value is calculated when you compute the signature you don't have to calculate it yourself.

When you call the ComputeSignature method what it does is take the SignedInfo node and digest it. Your references are inside this node so you will get the signature value containing the info of all your references

This is how the ComputeSignature method gets the digest value of the signedinfo node,using this value it calculates the signature value:

XmlElement e = this.SignedInfo.GetXml(); //get the signedinfo nodes
document.AppendChild(document.ImportNode(e, true));
Transform canonicalizationMethodObject=this.SignedInfo.CanonicalizationMethodObject;
canonicalizationMethodObject.LoadInput(document);
canonicalizationMethodObject.GetDigestedOutput(hash); //digest the signedinfo node