How to make WCF Client conform to specific WS-Security - sign UsernameToken and SecurityTokenReference

28,578

Finally sorted the problem today. In terms of terminology, it is not the SecurityTokenReference that I need to sign, but the Binary Security Token.

In order to do this I needed to hide the certificates for Initiator and Recipient and add a signed supporting token.

I went back to using configuration to create and sign the message, rather than trying to add the signature manually.

Other problem that would have stopped this from working is that I had an incorrect namespace on my custom 'Messaging' header, so be mindful of namespaces, I didn't think they would be as important as what they are.

The code to create the binding follows

    private System.ServiceModel.Channels.Binding GetCustomBinding()
    {
        System.ServiceModel.Channels.AsymmetricSecurityBindingElement asbe = new AsymmetricSecurityBindingElement();
        asbe.MessageSecurityVersion = MessageSecurityVersion.WSSecurity11WSTrust13WSSecureConversation13WSSecurityPolicy12;

        asbe.InitiatorTokenParameters = new System.ServiceModel.Security.Tokens.X509SecurityTokenParameters { InclusionMode = SecurityTokenInclusionMode.Never };
        asbe.RecipientTokenParameters = new System.ServiceModel.Security.Tokens.X509SecurityTokenParameters { InclusionMode = SecurityTokenInclusionMode.Never };
        asbe.MessageProtectionOrder = System.ServiceModel.Security.MessageProtectionOrder.SignBeforeEncrypt;

        asbe.SecurityHeaderLayout = SecurityHeaderLayout.Strict;
        asbe.EnableUnsecuredResponse = true;
        asbe.IncludeTimestamp = false;
        asbe.SetKeyDerivation(false);
        asbe.DefaultAlgorithmSuite = System.ServiceModel.Security.SecurityAlgorithmSuite.Basic128Rsa15;
        asbe.EndpointSupportingTokenParameters.Signed.Add(new UserNameSecurityTokenParameters());
        asbe.EndpointSupportingTokenParameters.Signed.Add(new X509SecurityTokenParameters());

        CustomBinding myBinding = new CustomBinding();
        myBinding.Elements.Add(asbe);
        myBinding.Elements.Add(new TextMessageEncodingBindingElement(MessageVersion.Soap11, Encoding.UTF8));

        HttpsTransportBindingElement httpsBindingElement = new HttpsTransportBindingElement();
        httpsBindingElement.RequireClientCertificate = true;
        myBinding.Elements.Add(httpsBindingElement);

        return myBinding;
    }

When using the binding, I set the ClientCredentials UserName, ServiceCertificate and ClientCertificate, and all works as expected.


Using the code is as follows

using (CredentialingService.SOAPPortTypeClient client = GetCredentialingClient())
{
    client.Open();
    etc....
}

private static CredentialingService.SOAPPortTypeClient GetCredentialingClient()
{
    CredentialingService.SOAPPortTypeClient client = new CredentialingService.SOAPPortTypeClient(GetCustomBinding(), new EndpointAddress(new Uri(Settings.AppSettings.B2BUrl), new DnsEndpointIdentity(Settings.AppSettings.B2BDNSEndpoint), new AddressHeaderCollection()));
    client.Endpoint.Contract.ProtectionLevel = System.Net.Security.ProtectionLevel.None;
    SetClientCredentialsSecurity(client.ClientCredentials);

    return client;
}

where GetCustomBinding is specified in my post

SetClientCredentialsSecurity is where the certificate is set, and is as follows

private static void SetClientCredentialsSecurity(ClientCredentials clientCredentials)
{
    clientCredentials.UserName.UserName = Settings.AppSettings.B2BUserName;
    clientCredentials.UserName.Password = Settings.AppSettings.B2BPassword;

    string directoryName = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
    clientCredentials.ServiceCertificate.DefaultCertificate = new X509Certificate2(Path.Combine(directoryName, Settings.AppSettings.B2BServerCertificateName));
    clientCredentials.ClientCertificate.Certificate = new X509Certificate2(Path.Combine(directoryName, Settings.AppSettings.B2BClientCertificateName), Settings.AppSettings.B2BClientCertificatePassword);
}

Hopefully that makes it a bit clearer

Share:
28,578

Related videos on Youtube

Steve B
Author by

Steve B

Updated on June 29, 2020

Comments

  • Steve B
    Steve B almost 4 years

    I need to create a wcf client to call a service that I have no control over.

    I have been given a wsdl and a working soapui project.

    The service uses both a username/password and a x509 certificate.


    UPDATE

    I now understand what the problem is, but am still unsure what steps I need to take to be able to create the required message, so any help would be much appreciated.

    I need to sign both the UsernameToken and the SecurityTokenReference.

    The code I had to create the custom binding has been removed from this post as its no longer used. I no longer add a SecurityBindingElement to the binding, instead I add a new behaviour that writes the security element into the header.

    So the security node is created from scratch by subclassing the SignedXml class, adding signing references and then calling ComputeSignature to create the Signature node within the Security header.

    You need to pass the xml to sign into the SignedXml constructor for this to work. It is no problem passing in the UsernameToken and this appears to be signed correctly.

    The problem is that the SecurityTokenReference is only created when ComputeSignature() is called, so I'm not able to add a signing Reference to this element, as it does not exist at the time it is required (within the overridden GetIdElement method of SignedXml which is called prior to ComputeSignature())


    The code I'm using to create the signature block to insert into the Security header is as follows

       string certificatePath = System.Windows.Forms.Application.StartupPath + "\\" + "Certs\\sign-and-    enc.p12";
    
        XmlDocument xd = new XmlDocument();
        xd.LoadXml(xml);
    
        // Set Certificate 
        System.Security.Cryptography.X509Certificates.X509Certificate2 cert = new X509Certificate2(certificatePath, "password");
        MySignedXml signedXml = new MySignedXml(xd);
        signedXml.SigningKey = cert.PrivateKey;
    
        // Create a new KeyInfo object. 
        KeyInfo keyInfo = new KeyInfo();
        keyInfo.Id = "";
    
        MemoryStream keyInfoStream = new MemoryStream();
        XmlWriter keyInfoWriter = XmlWriter.Create(keyInfoStream);
    
        WSSecurityTokenSerializer.DefaultInstance.WriteKeyIdentifierClause(keyInfoWriter, new LocalIdKeyIdentifierClause("token_reference", typeof(X509SecurityToken)));   
    
        keyInfoWriter.Flush();
        keyInfoStream.Position = 0;
        XmlDocument keyInfoDocument = new XmlDocument();
        keyInfoDocument.Load(keyInfoStream);
    
        XmlAttribute attrib = keyInfoDocument.CreateAttribute("ValueType");
        attrib.InnerText = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3";
        keyInfoDocument.ChildNodes[1].ChildNodes[0].Attributes.Append(attrib);
    
        KeyInfoNode keyInfoNode = new KeyInfoNode();
        keyInfoNode.LoadXml(keyInfoDocument.DocumentElement);
        keyInfo.AddClause(keyInfoNode);
    
        // Add the KeyInfo object to the SignedXml object. 
        signedXml.KeyInfo = keyInfo;
    
        // Need to use External Canonicalization method. 
        signedXml.SignedInfo.CanonicalizationMethod = "http://www.w3.org/2001/10/xml-exc-c14n#";
    
        // Create a reference to be signed. 
        Reference reference = new Reference();
        reference.Uri = "#UsernameToken-1";
    
        XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
        env.Algorithm = "http://www.w3.org/2001/10/xml-exc-c14n#";
        reference.AddTransform(env);
        reference.DigestMethod = "http://www.w3.org/2000/09/xmldsig#sha1";
        signedXml.AddReference(reference);
    
    
        Reference reference2 = new Reference();
        reference2.Uri = "#token_reference";
        XmlDsigEnvelopedSignatureTransform env2 = new XmlDsigEnvelopedSignatureTransform();
        env2.Algorithm = "http://www.w3.org/2001/10/xml-exc-c14n#";
        reference2.AddTransform(env2);
        reference2.DigestMethod = "http://www.w3.org/2000/09/xmldsig#sha1";
        signedXml.AddReference(reference2);
    
        // Add the Signature Id 
        signedXml.Signature.Id = "MYSIG_ID";
    
        // Compute the signature. 
        signedXml.ComputeSignature();
    
        XmlElement xmlDigitalSignature = signedXml.GetXml();
    

    where the xml variable is the the UsernameToken xml string, and the MySignedXml class is a subclassed SignedXml with the GetIdElement method overridden (to try to find and correctly refreence the non-existant SecurityTokenReference)

    I've spend days researching and testing this now, and unfortunately the company supplying the service isn't any help - but I need to use their service.

    Full working soap message (soapUI project)

    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:XXXXX">
       <soapenv:Header xmlns:ebxml="http://docs.oasis-open.org/ebxml-msg/ebms/v3.0/ns/core/200704/">
       <wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
        <wsse:BinarySecurityToken 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="CertId-D05E596B5ABC341FEB13505090224061" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">MIIEnDCCBAWgAwIBAgIBAjANBgkqhkiG9w0BAQUFADCBxDELMAkGA1UEBhMCTloxDTALBgNVBAgTBFdHVE4xEzARBgNVBAcTCldlbGxpbmd0b24xGDAWBgNVBAoTD0lSRFN0dWR5bGlua0IyQjEUMBIGA1UECxMLRGV2ZWxvcG1lbnQxHjAcBgNVBAMTFWRldmNhLmIyYi5pcmQuZ292dC5uejEXMBUGA1UEKRMORGV2ZWxvcG1lbnQgQ0ExKDAmBgkqhkiG9w0BCQEWGWNocmlzLnNjaHVsdHpAaXJkLmdvdnQubnowHhcNMTEwOTE1MDIwNjIwWhcNMjEwOTEyMDIwNjIwWjCByTELMAkGA1UEBhMCTloxDTALBgNVBAgTBFdHVE4xEzARBgNVBAcTCldlbGxpbmd0b24xGDAWBgNVBAoTD0lSRFN0dWR5bGlua0IyQjEUMBIGA1UECxMLRGV2ZWxvcG1lbnQxJTAjBgNVBAMTHHNpZ24tYW5kLWVuYy5kZXYuaXJkLmdvdnQubnoxFTATBgNVBCkTDHNpZ24tYW5kLWVuYzEoMCYGCSqGSIb3DQEJARYZY2hyaXMuc2NodWx0ekBpcmQuZ292dC5uejCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAykyZHVnXjsG220zB3kNOsGBeGP2rdNbLlIqW1D8yZO1fcj3/RhRiqsopbUrb8AU1ClpfhbH2K68kg7V91VAY0qrwNxP+pPPo1vYKMU6pT38qJGQzffr+iV2BCJshZvSk9E7QSWO5mFNstdg19xc+5ST1Lgb3fefuRG2KZVxPx0sCAwEAAaOCAZUwggGRMAkGA1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQDAgZAMDQGCWCGSAGG+EIBDQQnFiVFYXN5LVJTQSBHZW5lcmF0ZWQgU2VydmVyIENlcnRpZmljYXRlMB0GA1UdDgQWBBSczRKXKPe3Sin7eFrVXfI7MXckzzCB+QYDVR0jBIHxMIHugBSLWxPSZd9otEj16vhLyovMCI9OMaGByqSBxzCBxDELMAkGA1UEBhMCTloxDTALBgNVBAgTBFdHVE4xEzARBgNVBAcTCldlbGxpbmd0b24xGDAWBgNVBAoTD0lSRFN0dWR5bGlua0IyQjEUMBIGA1UECxMLRGV2ZWxvcG1lbnQxHjAcBgNVBAMTFWRldmNhLmIyYi5pcmQuZ292dC5uejEXMBUGA1UEKRMORGV2ZWxvcG1lbnQgQ0ExKDAmBgkqhkiG9w0BCQEWGWNocmlzLnNjaHVsdHpAaXJkLmdvdnQubnqCCQDL/qDdlx2j6DATBgNVHSUEDDAKBggrBgEFBQcDATALBgNVHQ8EBAMCBaAwDQYJKoZIhvcNAQEFBQADgYEAS4ZPIVVpgTOGN4XcIC3SiYlxF8wYg7qnUhH5wJsAD3VCKfj68j9FSJtdBWLlWvvRxEoDP2lZ0IbFl6Rjnl+2ibzFnyac2G1AVm5mwPrNKHBQJ9J5eDJi0iUVY7Wphz86tKnqj34GvlHPNXmrF7oGEcDhPwK0T8zRdE/pvKIUiJc=</wsse:BinarySecurityToken>
        <ds:Signature Id="Signature-2" 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-sha1"/>
                <ds:Reference URI="#CertId-D05E596B5ABC341FEB13505090224061">
                    <ds:Transforms>
                        <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                    </ds:Transforms>
                    <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                    <ds:DigestValue>3iVAUEAt8vAb7Ku+jf2gwSkSm0Q=</ds:DigestValue>
                </ds:Reference>
                <ds:Reference URI="#UsernameToken-1">
                    <ds:Transforms>
                        <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                    </ds:Transforms>
                    <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                    <ds:DigestValue>r4HLEAWJldJwmEmcAqV6Y8rnTPE=</ds:DigestValue>
                </ds:Reference>
            </ds:SignedInfo>
            <ds:SignatureValue>
            YGh2I3VcukqjT0O7hKItiykWN5tlID18ZXRCwQjXriHmnVsO4wGcHjWfmhuNDecq+xRN+SjG8E7M
            2Rx/5/BbFKbVlNOkQOSbSxIs1YT9GaThK16pMrX5KRkkJme1W3R0pGIIQh6gGRSUf79RZUIYxyVl
            LqdIe561TXXDdtbt/6Q=
            </ds:SignatureValue>
            <ds:KeyInfo Id="KeyId-D05E596B5ABC341FEB13505090224372">
                <wsse:SecurityTokenReference wsu:Id="STRId-D05E596B5ABC341FEB13505090224373" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
                    <wsse:Reference URI="#CertId-D05E596B5ABC341FEB13505090224061" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/></wsse:SecurityTokenReference>
            </ds:KeyInfo>
        </ds:Signature>
        <wsse:UsernameToken wsu:Id="UsernameToken-1" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
            <wsse:Username>XXXXXX</wsse:Username>
            <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">XXXXXXX</wsse:Password>
            </wsse:UsernameToken>
        </wsse:Security>
      <ebxml:Messaging xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
         <ebxml:UserMessage>
            <ebxml:MessageInfo>
               <ebxml:Timestamp>2002-02-02T14:18:02.0Z</ebxml:Timestamp>
               <ebxml:MessageId>bf9433d9-c6e9-4c12-9c98-724008a09c21</ebxml:MessageId>
            </ebxml:MessageInfo>
            <ebxml:PartyInfo>
               <ebxml:From>
                  <ebxml:PartyId type="identifier">Trading Partner X</ebxml:PartyId>
                  <ebxml:Role>Provider</ebxml:Role>
               </ebxml:From>
               <ebxml:To>
                  <ebxml:PartyId type="identifier">XXXXXXX</ebxml:PartyId>
                  <ebxml:Role>Requestor</ebxml:Role>
               </ebxml:To>
            </ebxml:PartyInfo>
            <ebxml:CollaborationInfo>
               <ebxml:AgreementRef>urn:XXXXXXXXX</ebxml:AgreementRef>
               <ebxml:Service type="Web Service">urn:XXXXXXXX</ebxml:Service>
               <ebxml:Action>customerInformation</ebxml:Action>
               <ebxml:ConversationId>e302426a-b2d9-4ff1-a14b-fbbc2f40a017</ebxml:ConversationId>
            </ebxml:CollaborationInfo>
         </ebxml:UserMessage>
      </ebxml:Messaging>
       </soapenv:Header>
       <soapenv:Body>
      <urn:ConnectionTest>
         <Message>Bonjour</Message>
      </urn:ConnectionTest>
       </soapenv:Body>
    </soapenv:Envelope>
    

    We were supplied with some documentation. The security section is copied below

    The following security must be applied to each request in the following order: A wsse:UsernameToken must be included and contain:

    1. The Agent‟s Portal Username for the value of the wsse:Username element
    2. The Agent‟s Portal Password for the value of the wsse:PasswordText (clear text) in the Password element A Signature block: Digital Signatures are created using x509 certificates. Each software provider is required to hold and protect a valid certificate and private key issued by [To Be Determined]. With each service request the software must:

    3. Include the Certificate that matches the private key used for signing as a wsse:BinarySecurityToken and use it as the wsse:SecurityTokenReference for the Signature

    4. Sign the wsse:BinarySecurityToken
    5. Sign the wsse:UsernameToken
    6. Use Canonicalization Method Algorithm: http://www.w3.org/2001/10/xml-exc-c14n#
    7. Use Signature Method Algorithm: http://www.w3.org/2000/09/xmldsig#rsa-sha1
    8. Use Digest Method Algorithm: http://www.w3.org/2000/09/xmldsig#sha1

    Update

    Image of the SoapUI configuration I was initially given SoapUIConfig

    • Glenn Ferrie
      Glenn Ferrie over 11 years
      I think the first thing you need to do is move some of that client service config to a config file. I am going to give it a shot in a test proj. Did they provide with certicifates?
    • Steve Py
      Steve Py over 11 years
      This sounds a bit odd. I can't see I've ever come across that sort of security model. Typically if you're using user names and passwords for message authentication you'd be using Transport level security. If you were using Message level security then a nominated certificate would be used, not user name and password.
    • Steve B
      Steve B over 11 years
      Thanks for your comments already, I'm adding a few further notes to my question in response
    • Steve B
      Steve B over 11 years
      I think I am getting further but have still not solved the issue. In the soapui project, if I remove the Token from the signature parts on the outgoing ws-security configuration then I get the same error in Fiddler (Could not validate signature using any of the supported token types). My thought now is that I'm not signing this 'Token' part in the wcf message correctly. From my reading I think this token is actually the BinarySecurityToken but when I compare my message to the working soapui message the values for this element are the same. Is anyone able to give me some pointers here? SB
    • Yaron Naveh
      Yaron Naveh over 11 years
      please publish the full soapui and c# request messages
    • Steve B
      Steve B over 11 years
      Hi @Yaron, I've published the full messages above. As I've said in the updated posting, I might be on the wrong track but it looks like one of my Reference URI values may be pointing to the message body rather than the SecurityTokenReference, but I'm not sure how to change this.
    • Steve B
      Steve B over 11 years
      I should also add that there is a custom header added to the message. I don't think this relates to my problem though
    • Yaron Naveh
      Yaron Naveh over 11 years
      yes, the problem is that wcf does not sign the certificate. try to remove the certificater signature from soapUI and see if you get the same error. if so maybe you could force wcf to sign the token using something similar to this: blogs.msdn.com/b/drnick/archive/2007/01/18/… and if this does not work then either you need to sign everything yourself using SignedInfo or you can simply copy the relevent soap UI headers and hard code them inside Wcf using a custom message inspector.
    • Steve B
      Steve B over 11 years
      I've just re-written the question to hopefully make it clearer and more succinct. I've changed direction by now trying to create the security header manually. Possibly getting closer to the required message, but I'm not there yet.
    • VoodooChild
      VoodooChild over 8 years
      @SteveB: How did you include the custom soap headers to your message? And for the certificates, how did you configure it in soapUI?
    • Steve B
      Steve B over 8 years
      Hi @VoodooChild. Trying to remember back to what I did here. I think, from memory, I started off trying to use a ClientMessageInspector to inject the header info on BeforeSendRequest
    • Steve B
      Steve B over 8 years
      Hi @VoodooChild. Trying to remember back to what I did here. I think, from memory, I started off trying to use a ClientMessageInspector to inject the header info on BeforeSendRequest, but I never managed to get this working as my messages were always rejected by the service. In the end, I just used configuration settings in my GetCustomBinding method (below) which gave me what was required. I can send you the full code if you thought it would help? Regarding SoapUI, I was given a working project so don't think I had to do any configuration. I'' try to add a screenshot of the config settings
    • VoodooChild
      VoodooChild over 8 years
      @SteveB - thanks for responding! I have provided more details to my issue explained here stackoverflow.com/q/32703632/247184 If you have some bandwidth, please take a look. I have the wsse header like I want in soapUI, but I need help setting the binding and configuration from the wcf client side.
  • Steve B
    Steve B over 11 years
    Thanks for your response @Kaveh but I'm not sure how that is really different to what I'm doing now (other than the 'using' statement) - unless I'm missing something? I've tried much of what Yaron mentions above. The hard coding everything didn't work, but I think now that an aproach may be to hard code the header with the exception of the Signature node, and then try and sign the bits that need signing via code. I'll try this over the coming days and will post back anything I find out.
  • Kaveh Shahbazian
    Kaveh Shahbazian over 11 years
    I didn't see the first line in your code. Try adding that line to your code and IF your problem - as I guess - is about invalid certificate, it will be solved.
  • Steve B
    Steve B over 11 years
    I see, thanks @Kaveh I missed your point there. I do have that line in my code, but left it out of the snippet that I posted above. I probably should have included it to make things clear. Unfortunately having that line of code included has no effect on my issue.
  • Habeeb
    Habeeb over 8 years
    Where do you specify the Certificate to Sign the document?
  • Steve B
    Steve B over 8 years
    @Habeeb I've added to my post, which will hopefully make everything a bit clearer