Signing SOAP Messages Using X.509 Certificate from WCF Service for Java Web Service

This is my first question on the Internet. Hope this makes sense.

I saw several blogs related to this problem over the Internet, and I tried several of the ideas presented in them without success. Here is my situation:

I have a web application that calls the WCF web service, which then calls the Java web service. All of them are on different servers. The call between the WCF web service for the java web service does not exceed https, since the certificate will be enough to identify the caller (hence the message security).

  • Java Web Service (black box)

The Java web service requires a signed message and works as follows:
Before processing each request, the handler intercepts all incoming messages and performs the following verification rules:
1. The message contains a security header
2. Does the message have a valid security header identifier
3. Is the message signed correctly? 4. Does the message have a KeyInfo x.509 certificate
5. Is the certificate issued from a trusted CA configuration based on 6. Is the certificate valid (not expired, revoked)
7. The certificate contains the correct policy identifier

As soon as all these steps are confirmed, the message can be processed, if any step fails, a soap message exception will be returned.

The SOAP security header should verify compliance with the xxx ... w3.org/TR/SOAP-dsig/ specification.

The most complete description can be found here: xxx ... ibm.com/developerworks/webservices/library/ws-security.html This IBM article lists information about each WS-Security header, and it also has an example of a signed SOAP message.

When signing a SOAP message, you must also add the x.509 certificate to the KeyInfo message, this is necessary to verify the certificate.

The SOAP request should look like this:

<?xml version="1.0" encoding="UTF-8"?> <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"> <S:Header> <ds:Signature xmlns:ds="xxx...w3.org/2000/09/xmldsig#" Id="Signature001"> <ds:SignedInfo> <ds:CanonicalizationMethod Algorithm="xxx...w3.org/TR/2001/REC-xml-c14n-20010315"/> <ds:SignatureMethod Algorithm="xxx...w3.org/2000/09/xmldsig#rsa-sha1"/> <ds:Reference URI=""> <ds:Transforms> <ds:Transform Algorithm="xxx...w3.org/2000/09/xmldsig#enveloped-signature"/> </ds:Transforms> <ds:DigestMethod Algorithm="xxx...w3.org/2000/09/xmldsig#sha1"/> <ds:DigestValue>soe1PnaGXVGrsauC61JSHD+uqGw=</ds:DigestValue> </ds:Reference> <ds:Reference URI="#KeyInfo001"> <ds:DigestMethod Algorithm="xxx...w3.org/2000/09/xmldsig#sha1"/> <ds:DigestValue>Y9SRPQ9TcDu+GazO3LFwodEdhaA=</ds:DigestValue> </ds:Reference> </ds:SignedInfo> <ds:SignatureValue>jBX/8XkY2aCte7qgXEp1sbNWmQcK/90iVL58sAvwYAEcBABGzOk2agxR0HvWrNa6ixkocAQ205lggwOxnxZJvoVozVYAAjcLtayPBOUYrnSEBFrwKWP/vxgvUDRIdXeIuw5GLY87NrTQMm1Ehf/HvMX9hTBJn4Nm8RdDiUmPcIo=</ds:SignatureValue> <ds:KeyInfo Id="KeyInfo001"> <ds:X509Data> <ds:X509Certificate>MIIEbZCCA1WgAwIBAgIES1XpMjANBgkqhkiG9w0BAQUFADBYMRUwEwYKCZImiZPyLGQBGRYFbG9jYWwxFzAVBgoJkiaJk/IsZAEZFgdlbnRydXN0MRIwEAYDVQQDEwllbnRydXN0U00xEjAQBgNVBAMTCWVudHJ1c3RDQTAeFw0xMDA0MjIxMDQ4MDBaFw0xMzA0MjIxMTE4MDBaMGoxFTATBgoJkiaJk/IsZAEZFgVsb2NhbDEXMBUGCgmSJomT8ixkARkWB2VudHJ1c3QxEjAQBgNVBAMTCWVudHJ1c3RTTTESMBAGA1UEAxMJZW50cnVzdENBMRAwDgYDVQQDEwdSYnMgUmJzMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMf88L2JjLPG1hNmTA/KBiC53WVwS2WU9Jh3lC1Rob6RMzOojomZ/dNrvSRB6nzWeXJpZXwik4XFrsAq24By2SZpLTO4p8Vcq71mTAfDu33cnO49Au2pwNvcMn5qIKBk1Xx+oVb4fzK9ncTRu7bW46HsIYth+qkGhbI2JEHwr/zwIDAQABo4IBrzCCAaswCwYDVR0PBAQDAgeAMCsGA1UdEAQkMCKADzIwMTAwNDIyMTA0ODAwWoEPMjAxMjA1MjgxNTE4MDBaMCMGA1UdIAQcMBowCwYJYIZIAYb6awoEMAsGCSqGSIb2fQdLAzAbBgNVHQkEFDASMBAGCSqGSIb2fQdEHTEDAgEBMIHGBgNVHR8Egb4wgbswb6BtoGukaTBnMRUwEwYKCZImiZPyLGQBGRYFbG9jYWwxFzAVBgoJkiaJk/IsZAEZFgdlbnRydXN0MRIwEAYDVQQDEwllbnRydXN0U00xEjAQBgNVBAMTCWVudHJ1c3RDQTENMAsGA1UEAxMEQ1JMMTBIoEagRIZCZmlsZTovLy8vTVNJREhVLTQ0NUE0RkVFL0NSTC9lbnRydXN0Y2FfZW50cnVzdHNtX2xvY2FsX2NybGZpbGUuY3JsMB8GA1UdIwQYMBaAFBvSL6cPz8L5shubV58yf0pczKzuMB0GA1UdDgQWBBT1/j6OSS8FTjwqluvew16sv7h+VzAJBgNVHRMEAjAAMBkGCSqGSIb2fQdBAAQMMAobBFY4LjADAgSwMA0GCSqGSIb3DQEBBQUAA4IBAQBXxRIA4HUvGSw4L+4uaR51pY4ISjUQWo2Fh7FYBMt29NsKCTdur1OWVVdndt1yjXP4yWXxoAhHtvZL+XNALUFlR2HAWiXuL1nRcxHkB98N5gPqQzW/lJk9cLtL4hVp28EiEpgmKT3I3NP2Pdb2G5MMOdvQ/GFb2y6OwblR8ViPQ8B2aHWzXMrH+0qadPAuBhXyAohwb+mMuYT/ms6xpGi1NMYuYMf6XONz9GkZgnGnMwa+9CCQws1HNz8WYHtmFIxLsVuEWc/0a1vg4IYX1Ds/ttyhJGTVXOSJSkBz8kRyj1pNBDdc1KeG8M++O8m8VgRTJvYaPc7NMiclISukGpea</ds:X509Certificate> </ds:X509Data> </ds:KeyInfo> </ds:Signature> </S:Header> <S:Body Id="ABC"> <ns2:createUser xmlns:ns2="http://webservice.rbs.emea.ps.entrust.com/" xmlns:ns3="http://webservice.rbs.emea.ps.entrust.com/types/CertificateException" xmlns:ns4="http://webservice.rbs.emea.ps.entrust.com/types/UserException"> <userID>0061020051</userID> </ns2:createUser> </S:Body> </S:Envelope> 
  • WCF web service

I have one server certificate (p7b format from a trusted CA) that I installed where my WCF web service workstation (dev) is used using the mmc Certificate snap-in (the certificate is currently located in trusted publishers). I don’t think I need another certificate on the Java server, as the answer should be clear (neither signed nor encrypted). I'm still a little confused by this certificate and certificates in general, as it seems to contain only the public key.

Here is the app.config app of my test project:

 <client> <endpoint address="http://entrust-user-certification-uat.fm.rbsgrp.net/rbs/WebAS" behaviorConfiguration="endpointCredentialsBehavior" binding="wsHttpBinding" bindingConfiguration="WebAsServicePortTypeBinding" contract="IWebAsServicePortType" name="WebAsServicePortType"> <!--<identity> <dns value="entrust-user-certification-uat.fm.rbsgrp.net" /> </identity>--> </endpoint> </client> <bindings> <wsHttpBinding> <binding name="WebAsServicePortTypeBinding" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false"> <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384" /> <security mode="Message"> <message clientCredentialType="Certificate" negotiateServiceCredential="false" establishSecurityContext="false" /> </security> </binding> </wsHttpBinding> </bindings> <behaviors> <endpointBehaviors> <behavior name="endpointCredentialsBehavior"> <clientCredentials> <clientCertificate findValue="entrust-user-certification-uat.fm.rbsgrp.net" storeLocation="LocalMachine" storeName="TrustedPublisher" x509FindType="FindBySubjectName"></clientCertificate> <serviceCertificate> <!-- Setting the certificateValidationMode to PeerOrChainTrust means that if the certificate is in the user Trusted People store, then it will be trusted without performing a validation of the certificate issuer chain. This setting is used here for convenience so that the sample can be run without having to have certificates issued by a certificate authority (CA). This setting is less secure than the default, ChainTrust. The security implications of this setting should be carefully considered before using PeerOrChainTrust in production code. --> <authentication certificateValidationMode="None" revocationMode="NoCheck" trustedStoreLocation="LocalMachine"/> </serviceCertificate> </clientCredentials> </behavior> </endpointBehaviors> </behaviors> 

When I did a simple test:
WebAS entrustService = new WebAS ();
ActivationCodes certCodes = entrustService.createUser ("testNomad");
I have a mistake:
failed: System.Web.Services.Protocols.SoapException: javax.xml.soap.SOAPException: Signature element not found in soap message

How can I force the process of signing each message? I thought I could do this through the WCF configuration quite easily. Any help would be greatly appreciated!

+5
source share
2 answers

OK After several attempts and errors, this solution uses the SignedXml template and IClientMessageInspector / BeforeSendRequest. Many thanks to Yaron Naveh for his relevant suggestions.

 // Sign an XML request and return it public static string SignRequest(string request, string SubjectName, string Signature, string keyInfoRefId) { if (string.IsNullOrEmpty(request)) throw new ArgumentNullException("request"); if (string.IsNullOrEmpty(SubjectName)) throw new ArgumentNullException("SubjectName"); // Load the certificate from the certificate store. X509Certificate2 cert = GetCertificateBySubject(SubjectName); // Create a new XML document. XmlDocument doc = new XmlDocument(); // Format the document to ignore white spaces. doc.PreserveWhitespace = false; // Load the passed XML doc.LoadXml(request); // Add the declaration as per Entrust sample provided -don't think it necessary though if (!(doc.FirstChild is XmlDeclaration)) { XmlDeclaration declaration = doc.CreateXmlDeclaration("1.0", "UTF-8", string.Empty); doc.InsertBefore(declaration, doc.FirstChild); } // Remove the Action (MustUnderstand). // TODO: Need to find a more elegant way to do so XmlNode headerNode = null; XmlNodeList nodeList = doc.GetElementsByTagName("Action"); if (nodeList.Count > 0) { headerNode = nodeList[0].ParentNode; headerNode.RemoveChild(nodeList[0]); } // Set the body id - not in used but could be useful at a later stage of this project XmlNamespaceManager ns = new XmlNamespaceManager(doc.NameTable); ns.AddNamespace("s", "http://schemas.xmlsoap.org/soap/envelope/"); XmlElement body = doc.DocumentElement.SelectSingleNode(@"//s:Body", ns) as XmlElement; if (body == null) throw new ApplicationException("No body tag found"); body.RemoveAllAttributes(); // no need to have namespace body.SetAttribute("Id", "ABC"); // Body Id could be passed as a param // Create a custom SignedXml object so that we could sign the keyinfo CustomSignedXml signedXml = new CustomSignedXml(doc); // Add the key to the SignedXml document. signedXml.SigningKey = cert.PrivateKey; // Create a new KeyInfo object. KeyInfo keyInfo = new KeyInfo(); keyInfo.Id = keyInfoRefId; // Load the certificate into a KeyInfoX509Data object // and add it to the KeyInfo object. KeyInfoX509Data keyInfoData = new KeyInfoX509Data(); keyInfoData.AddCertificate(cert); keyInfo.AddClause(keyInfoData); // Add the KeyInfo object to the SignedXml object. signedXml.KeyInfo = keyInfo; // Create a reference to be signed. Reference reference = new Reference(); reference.Uri = ""; // Add an enveloped transformation to the reference. XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform(); reference.AddTransform(env); // Add the reference to the SignedXml object. signedXml.AddReference(reference); Reference reference2 = new Reference(); reference2.Uri = "#" + keyInfoRefId; signedXml.AddReference(reference2); // Add the Signature Id signedXml.Signature.Id = Signature; // Compute the signature. signedXml.ComputeSignature(); // Get the XML representation of the signature and save // it to an XmlElement object. XmlElement xmlDigitalSignature = signedXml.GetXml(); // Append the Signature element to the XML document. if (headerNode != null) { headerNode.AppendChild(doc.ImportNode(xmlDigitalSignature, true)); } return doc.InnerXml; } public static X509Certificate2 GetCertificateBySubject(string CertificateSubject) { // Check the args. if (string.IsNullOrEmpty(CertificateSubject)) throw new ArgumentNullException("CertificateSubject"); // Load the certificate from the certificate store. X509Certificate2 cert = null; X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser); try { // Open the store. store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); // Find the certificate with the specified subject. cert = store.Certificates.Find(X509FindType.FindBySubjectName, CertificateSubject, false)[0]; // Throw an exception of the certificate was not found. if (cert == null) { throw new CryptographicException("The certificate could not be found."); } } finally { // Close the store even if an exception was thrown. store.Close(); } return cert; } 

and CustomSignedXml class:

 public class CustomSignedXml : SignedXml { public CustomSignedXml(XmlDocument doc) : base(doc) { return; } public override XmlElement GetIdElement(XmlDocument doc, string id) { // see if this is the key info being referenced, otherwise fall back to default behavior if (String.Compare(id, this.KeyInfo.Id, StringComparison.OrdinalIgnoreCase) == 0) return this.KeyInfo.GetXml(); else return base.GetIdElement(doc, id); } } 
+14
source

Can you capture the sending of messages by WCF? Btw. is the message security used by the Java service described in the WSDL - this will greatly simplify the situation.

According to your description, I think that your configuration is incorrect, because when the credentials of the certificate client are used, you need two certificates - the client certificate with the public and private keys and the server certificate with the public key.

It is also probably described in your requirements:

Does the message have a KeyInfo x.509 certificate

Is the certificate issued from a trusted CA based on

Why do you need to send back a service certificate that is already installed on this server? Why should a service check if its certificate is from a trusted CA? I assume that these requirements say that you should create a new certificate for your client.

But this is only an assumption, because real requirements are usually described in common languages ​​- WSDL + WS-Security statements.

Signature control and encryption are possible at several levels. First of all, each ServiceContract and MessageContract has a ProtectionLevel property, which is EncryptAndSign by default. You can change it to Sign .

0
source

Source: https://habr.com/ru/post/912153/


All Articles