3

Have been struggling for last few days with this error Authentication of type {http://service.soap.xcompany.com}AuthenticationHeader had undefined attribute {http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Id while invoking a service call from a C# WCF client (targeting .Net 4.5 framework) to a Java Soap Service hosted externally with end-to-end encryption (both client and service certificates are used). When I tested the service using SoapUI with a JKS file, request was processed successfully.

So to see what's difference between the two requests, I did the followings:

  1. Used Fiddler Inspector to capture two requests, one from SoapUI which was successful and one from C# which failed with 500 error
  2. Extracted these two Xml messages into two C# classes (named them RequestByJava and RequestByDotNet, respectively) using the VS2017 feature Edit/Paste Special/Paste Xml as Classes.
  3. Use XmlSerializer to de-serialize the two requests into the two objects of the types created in 2) and compared their properties.
  4. With the Soap error message in mind, I narrowed down the difference between two Authentication headers - interestingly there is one extra property "Id" in the RequestByDotNet object whereas the RequestByJava object does not have. And the 500 Soap error message seemed to indicate that there was a schema validation error due to that undefined element "Id"
  5. Also noticed that the RequestByDotNet.Header.Security.BinarySecurityToken.ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" but RequestByJava (SoapUI) has a different ValueType "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509PKIPathv1"
  6. Another difference, not sure it matters, is that the Request from .net codes has a "mustunderstand" value under the Header.Security set to true while the one from Java does not.

My questions are:

  1. Why is the difference?
  2. How can this be fixed without having to write a Java client?

Some codes used binding and endpoint behavior:

private static CustomBinding BuildCustomBinding()
    {
        var binding = new CustomBinding();

        var textMessageEncoding = new TextMessageEncodingBindingElement()
        {
            MessageVersion = MessageVersion.Soap11

        };

        var securityBindingElement =
            SecurityBindingElement.CreateMutualCertificateBindingElement(
                MessageSecurityVersion.WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10, true);


        binding.Elements.AddRange(textMessageEncoding, securityBindingElement, new HttpsTransportBindingElement());

        return binding;
    }


private static void CallAccountService()
    {
        //credential for test
        const string applId = "testuser"; 
        const string pwd = "password";


        //for client certificate, import client.pfx to LocalMachine's Trusted Root Certification Authorities and make sure the thumbprint matches 
        var client = new NOLWSAccountSvc.WSAccountv1Client(BuildCustomBinding(), GetAccountServiceEndpointAddress());
        client.ClientCredentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine,
            StoreName.Root, X509FindType.FindByThumbprint, "thumbprintvalue");

        //for service certificate, import service-provider.cer to same store location and store name and make sure the thumbprint matches 
        client.ClientCredentials.ServiceCertificate.SetDefaultCertificate(StoreLocation.LocalMachine, StoreName.Root,
            X509FindType.FindByThumbprint, "thumprintvalue");
        client.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode =
            X509CertificateValidationMode.PeerOrChainTrust;

        client.Open();
        var header = new NOLWSAccountSvc.AuthenticationHeader()
        {
            application_id = applId,
            password = pwd
        };

        var getActiveAccountsFunc = new NOLWSAccountSvc.getActiveAccounts() { applRef = "softact-dev", resetRows = true };

        try
        {
            var response = client.getActiveAccounts(header, getActiveAccountsFunc);
            Console.WriteLine(response.moreData);
        }
        catch (Exception ex)
        {

            Console.WriteLine(ex.Message);
        }

        finally
        {
            client.Close();
        }
    }

Thanks for your time! Your help will be highly appreciated.

2
  • Did you compare the bodies of the working request and the non working request. It looks like the two requests are both encrypted using 509 encryption. See wiki : en.wikipedia.org/wiki/X.509 Commented Aug 26, 2017 at 22:12
  • It looks like the naming convention of the certificate is different, but are equivalent : Profile of the X.509 v3 certificate standard, as specified in RFC 5280, commonly called PKIX for Public Key Infrastructure (X.509). There must be something wrong with the actual certificate. You have to look at the certificate itself. Commented Aug 26, 2017 at 22:21

2 Answers 2

1

@jdweng Yes, I did; here were two request bodies, first from .Net and 2nd from SoapUI:

.Net Request:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"><s:Header><h:Authentication u:Id="_2" xmlns:h="http://service.soap.xcompany.com" xmlns="http://service.soap.xcompany.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><application_id>testuserid</application_id><password>testpassword</password></h:Authentication><ActivityId CorrelationId="d7085e6f-b757-46e8-b3eb-319a51d568a3" xmlns="http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics">00000000-0000-0000-0000-000000000000</ActivityId><VsDebuggerCausalityData xmlns="http://schemas.microsoft.com/vstudio/diagnostics/servicemodelsink">uIDPo8DAzaQVkApDpl1Tc1YTHQwAAAAAMbeMEvBLCUqoD7kEDPHDKYukgggNOf5FtHBB/Sa7ggkACQAA</VsDebuggerCausalityData><o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><o:BinarySecurityToken u:Id="uuid-eb310312-396a-4d00-8922-f77de97138cb-3" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">MIIDYzCCAkugAwIBAgIEaGKzJDANBgkqhkiG9w0BAQsFADBiMQswCQYDVQQGEwJ1czEPMA0GA1UEChMGU3ByaW50MREwDwYDVQQLEwhQcm9qZWN0czEMMAoGA1UECxMDQk1QMQwwCgYDVQQLEwNUUEExEzARBgNV</o:BinarySecurityToken><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><Reference URI="#_1"><Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><DigestValue>WCpRwVjx89ceVctR8lp9LNGKHeA=</DigestValue></Reference><Reference URI="#_2"><Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><DigestValue>8/PErh8BL9To5zazpP9CbPFTAa8=</DigestValue></Reference></SignedInfo><SignatureValue>hOtpz7lXvZPPbBD6sV1hxyx3Hc39vj0q2GYKMd8oQbgTbbuKC7QKcZOjktqUxayrzc6h/V0j7Kx3APPONe4F3A2581nK4AQ72yYonsaeXQW0yzSxW/VTsN04uoqCP6IpKXqlAz40VeWGUPJOeGthCKy/9A+NSuqS</SignatureValue><KeyInfo><o:SecurityTokenReference><o:Reference ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" URI="#uuid-eb310312-396a-4d00-8922-f77de97138cb-3"/></o:SecurityTokenReference></KeyInfo></Signature></o:Security></s:Header><s:Body u:Id="_1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><getActiveAccounts xmlns="http://service.soap.xcompany.com"><applRef>dev</applRef><resetRows>false</resetRows></getActiveAccounts></s:Body></s:Envelope>

SoapUI Request:

(somehow it won't let me past whole xml here.. )

Sign up to request clarification or add additional context in comments.

Comments

1

Well, my colleague helped me figure out way to remove the extra headers from the request before it was posted to the Java SOAP service endpoint - the key was to use IClientMessageInspector and implement some logic in the BeforeSendRequest to remove the unwanted headers that were rejected by the service provider; then add a custom FormattingBehavior class to inherit from IEndpointBheavior and in the IEndPointBehavior.ApplyClientBehavior, attach the MyClientMessageInspector; finally add the customer endpoint behavior to the web service client. Here are the codes:

  1. Where and how to remove unwanted request headers:

    public class MyClientMessageInspector : IClientMessageInspector
    

    { public MyClientMessageInspector(ServiceEndpoint endpoint) { }

    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        //Console.WriteLine(request.ToString());
    
        var lstUnwantedStuff = new[]
        {
            new KeyValuePair<string, string>("Action", "http://www.w3.org/2005/08/addressing"),
            new KeyValuePair<string, string>("VsDebuggerCausalityData",
                "http://schemas.microsoft.com/vstudio/diagnostics/servicemodelsink")
        };
    
        foreach (var kv in lstUnwantedStuff)
        {
            var indexOfUnwantedHeader = request.Headers.FindHeader(kv.Key, kv.Value);
            if (indexOfUnwantedHeader>=0)
            {
                request.Headers.RemoveAt(indexOfUnwantedHeader);
            }
        }
    

    ...

  2. Where and how to use the custom ClientMessageInspector:

     internal class MyFaultFormatterBehavior : IEndpointBehavior
    

    { ...

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        clientRuntime.MessageInspectors.Add(new MyClientMessageInspector(endpoint));
    }
    

    }

  3. Where and how to attach custom EndpointBehavior:

     private static void CallAccountService()
    {
    
        var client = new WSAccountv1Client(BuildCustomBinding(), GetAccountServiceEndpointAddress());
    

    //Set client certificate
    client.ClientCredentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.Root, X509FindType.FindByThumbprint, "xxxxxxxxxx");

        //for service certificate
        client.ClientCredentials.ServiceCertificate.SetDefaultCertificate(StoreLocation.LocalMachine, StoreName.TrustedPeople,
            X509FindType.FindByThumbprint, "xxxxxxxxxxxxxxxxy");
        client.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode =
            X509CertificateValidationMode.PeerOrChainTrust;
    
        //add faultformattingbehavior so we can intercept the fault reply message
        client.Endpoint.EndpointBehaviors.Add(new MyFaultFormatterBehavior());
    
        client.Open();
        var header = new AuthenticationHeader()
        {
            application_id = applId,
            password = pwd
        };
    
        var getActiveAccountsFunc = new getActiveAccounts() { applRef = "test", resetRows = true };
    
        try
        {
            //MyClientMessageInspector.BeforeSendRequest is entered when this called is made
            var response = client.getActiveAccounts(header, getActiveAccountsFunc);
            Console.WriteLine(response.moreData);
        }
        catch (Exception ex)
        {
    
            Console.WriteLine(ex.Message);
        }
    
        finally
        {
            client.Close();
        }
    }
    
  4. What else? In the proxy classes, need to set the Authentication ProtectionLevel to None while on the Service level it needs to be set as ProtectionLevel.Sign:

Request level:

    [System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[System.ServiceModel.MessageContractAttribute(IsWrapped = false)]
public partial class getActiveAccountsRequest
{

    [System.ServiceModel.MessageHeaderAttribute(Namespace = "http://service.xcompany.com"
        , ProtectionLevel = System.Net.Security.ProtectionLevel.None
        )]
    public AuthenticationHeader Authentication;

Service (Interface) Level:

    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(Namespace = "http://service.xcompany.com",
    ConfigurationName = "WSAccount"
    , ProtectionLevel = ProtectionLevel.Sign
    )]
public interface WSAccount
{

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.