Search code examples
c#web-serviceswcfsoapsha2

How do I properly sign a SOAP message with SHA2 in C#?


I have the following requirements from a 3rd party service that I'm using

Transactions dealing with personal information and other sensitive data use transport layer security protection. The web service message will be transported over https (HTTP over SSL) and must adhere to Web Service (WS)-Security v1.1 standard. The WS-Security section of the service message must:

  • Be signed with x.509 certificate using a 2048 bit key size
  • Use SHA2 with RSA algorithm for encryption
  • Use C14 canonicalization.

I managed to get my message signed with the following code

someServiceRef.widjetClient client = null;

try
{
    X509Certificate2 signingCert = GetSigningCert();
    var bindings = new BasicHttpsBinding();
    bindings.Security.Mode = BasicHttpsSecurityMode.TransportWithMessageCredential;
    bindings.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.Certificate;

    client = new someServiceRef.widjetClient(
        bindings,
        new EndpointAddress(@"<URL OF SERVICE>"));

    client.ClientCredentials.ClientCertificate.Certificate = signingCert;
    client.ClientCredentials.ServiceCertificate.DefaultCertificate = signingCert;

    client.Open();

    var request = BuildRequest();

    var response = client.SayHello(request);

    Console.WriteLine(response);
}
finally
{
    if (client != null)
    {
        if (client.State == System.ServiceModel.CommunicationState.Faulted)
            client.Abort();
        else
            client.Close();
    }
}

The problem is that my message is being signed with sha1 instead of sha2. I'm trying to sign my message properly but the examples I found online have you generate the soap message then manually modify it with XML parsing and adding new nodes. I don't understand these examples and I'm trying to figure out a way to tell the service to do it for me. I have a sample of what the request signature should look like from the 3rd party below. I don't see anything in the client or binding class that would allow me to change things like the signature algorithm. How would I go about doing this?

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Header>
        <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" SOAP-ENV:mustUnderstand="1">
            <wsse:BinarySecurityToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" wsu:Id="XWSSGID-12324774331131695995061">XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</wsse:BinarySecurityToken>
            <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                <ds:SignedInfo>
                    <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
                    <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha2" />
                    <ds:Reference URI="#XWSSGID-1232477437326-1352495766">
                        <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha2" />
                        <ds:DigestValue>XXXXXXXXXXXXXXXXXXXXXX</ds:DigestValue>
                    </ds:Reference>
                    <ds:Reference URI="#XWSSGID-1232477437326-823787906">
                        <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha2" />
                        <ds:DigestValue>XXXXXXXXXXXXXXXXXXXXX</ds:DigestValue>
                    </ds:Reference>
                </ds:SignedInfo>
                <ds:SignatureValue>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</ds:SignatureValue>
                <ds:KeyInfo>
                    <wsse:SecurityTokenReference xmlns:wsse="http://www.w3.org/2000/09/xmldsig#" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsse:Id="XWSSGID-1232477437311698965010">
                        <wsse:Reference URI="#XWSSGID-12324774331131695995061" />
                        <ds:X509Data>
                            <ds:X509IssuerName>XXXXXXXXXXXXXXXXXXXXXXXXX</ds:X509IssuerName>
                            <ds:X509SerialNumber>XXXXXXXXXXXXXXXXXXX</ds:X509SerialNumber>
                        </ds:X509Data>
                    </wsse:SecurityTokenReference>
                </ds:KeyInfo>
            </ds:Signature>
            <wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="XWSSGID-1232477437326-823787906">
                <wsu:Created>2009-01-20T18:50:37.233Z</wsu:Created>
                <wsu:Expires>2009-01-20T18:50:42.233Z</wsu:Expires>
            </wsu:Timestamp>
        </wsse:Security>
    </SOAP-ENV:Header>
    <SOAP-ENV:Body xmlns:SOAP-ENV="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="XWSSGID-1232477437326-1352495766">
        BODY OF MESSAGE GOES HERE
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Solution

  • Got it working here is the final solution

    someServiceRef.widjetClient client = null;
    
    try
    {
        X509Certificate2 signingCert = GetSigningCert();
        var bindings = new BasicHttpsBinding();
        bindings.Security.Mode = BasicHttpsSecurityMode.TransportWithMessageCredential;
        bindings.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.Certificate;
        bindings.Security.Message.AlgorithmSuite = SecurityAlgorithmSuite.Basic256Sha256
    
        var elements = bindings.CreateBindingElements();
        elements.Find<SecurityBindingElement>().EnableUnsecuredResponse = true;
        var customBindings = new CustomBinding(elements);
    
        client = new someServiceRef.widjetClient(
            customBindings,
            new EndpointAddress(@"<URL OF SERVICE>"));
    
        client.ClientCredentials.ClientCertificate.Certificate = signingCert;
        client.ClientCredentials.ServiceCertificate.DefaultCertificate = signingCert;
    
        client.Open();
    
        var request = BuildRequest();
    
        var response = client.SayHello(request);
    
        Console.WriteLine(response);
    }
    finally
    {
        if (client != null)
        {
            if (client.State == System.ServiceModel.CommunicationState.Faulted)
                client.Abort();
            else
                client.Close();
        }
    }