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:
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:
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
Image of the SoapUI configuration I was initially given
To secure an application that runs exclusively on a Windows domain, you can use the default security settings of either the WSHttpBinding or the NetTcpBinding binding. By default, anyone on the same Windows domain can access WCF services. Because those users have logged on to the network, they are trusted.
A WCF service boasts of a robust security system with two security modes or levels so that only an intended client can access the services.
Windows Communication Foundation (WCF) is a SOAP message-based distributed programming platform, and securing messages between clients and services is essential to protecting data.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With