4

I am writing a Java client application that needs to sign a SOAP message (with 2 parts in it) and send it to a remote server before fetching the response. I managed to make successful calls in SoapUI (see request and screenshot below).

I have most of the code ready, but I keep getting an error message from the server saying:

SECU3504: Digital signature verification failure.
Signature failed core validation

Signature validation status: true
ref[#id-32e3db92-b6fd-42a5-b032-a0dc2a15ae82] validity status: false
ref[#id-2b67ce75-e25f-4f66-b265-80a0e31911ec] validity status: false 

Here is my Java code:

// Load KeyStore from .PFX certificate
KeyStore store = KeyStore.getInstance("PKCS12");
store.load(new FileInputStream(certificateFileName), certificatePassword.toCharArray());

// Build XML Document from String
String data = "..."; // Same request as in SoapUI
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
doc = dBuilder.parse(new ByteArrayInputStream(data.getBytes()));

// Load certificate
String alias = store.aliases().nextElement(); // There's only one
PrivateKeyEntry keyEntry = (PrivateKeyEntry)store.getEntry(alias, new KeyStore.PasswordProtection(password.toCharArray()));
X509Certificate cert = (X509Certificate) keyEntry.getCertificate();

// Add <wsse:Security> tag.
WSSecHeader secHeader = new WSSecHeader();
secHeader.setMustUnderstand(false);
secHeader.insertSecurityHeader(doc);

// It is not added to the right place, so move it under the right header       
Node n = doc.getFirstChild().getFirstChild().getFirstChild();           
Element elemEnvelope = (Element)doc.getElementsByTagName("soapenv:Envelope").item(0);
Element elemNewHeader = (Element)doc.getElementsByTagName("Header").item(0);
Element elemSoapHeader = (Element)doc.getElementsByTagName("soapenv:Header").item(0);           
Element elemBody = (Element)doc.getElementsByTagName("soapenv:Body").item(0);
Element elemHeader = (Element)doc.getElementsByTagName("ns1:CAISOWSHeader").item(0);
elemSoapHeader.insertBefore(n, elemHeader);
elemEnvelope.removeChild(elemNewHeader);

// Setup signing algorithm
WSSecSignature builder = new WSSecSignature();
builder.setX509Certificate(cert);
builder.setUserInfo(alias, password);
builder.setKeyIdentifierType(WSConstants.BST_DIRECT_REFERENCE);
builder.setSignatureAlgorithm(WSConstants.RSA_SHA1);
builder.setSigCanonicalization(WSConstants.C14N_EXCL_OMIT_COMMENTS);            
builder.setDigestAlgo(WSConstants.SHA1);
builder.setUseSingleCertificate(true);

// Set message parts to sign
List<WSEncryptionPart> parts = new ArrayList<WSEncryptionPart>();
WSEncryptionPart bodyPart1 = new WSEncryptionPart("Body", WSConstants.URI_SOAP11_ENV, "Content");
WSEncryptionPart bodyPart2 = new WSEncryptionPart("CAISOWSHeader", "http://www.caiso.com/soa/2006-09-30/CAISOWSHeader.xsd", "Content");
bodyPart1.setElement(elemBody);
bodyPart2.setElement(elemHeader);
parts.add(bodyPart1);
parts.add(bodyPart2);
builder.getParts().addAll(parts);           

// Set keystore and sign the document
Properties properties = new Properties();
properties.setProperty("org.apache.ws.security.crypto.provider", "org.apache.wss4j.common.crypto.Merlin");

Merlin crypto = (Merlin)CryptoFactory.getInstance(properties);
crypto.setKeyStore(store);

doc = builder.build(doc, crypto, secHeader);
String docStr = this.toString(doc); // This method generates a string of the Document.

// Send SOAP message
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
HttpPost httppost = new HttpPost(this.url);
HttpEntity entity = new ByteArrayEntity(docStr .getBytes("UTF-8"));
httppost.setEntity(entity); // Signed Body
httppost.addHeader("SOAPAction", "http://www.caiso.com/soa/retrieveConvergenceBidAwards_v2");

HttpResponse response = httpclient.execute(httppost);
String respStr = EntityUtils.toString(response.getEntity());

I managed to make successful calls in SoapUI with the following security settings:enter image description here

The request I put in SoapUI is the following (Dates/MessageId/Nonce are generated on the spot, but for the sake of readability, I will hardcode them here):

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:stan="http://www.caiso.com/soa/2006-06-13/StandardAttachmentInfor.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <soapenv:Header>
      <ns1:CAISOWSHeader xmlns:ns1="http://www.caiso.com/soa/2006-09-30/CAISOWSHeader.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" soapenv:actor="" soapenv:mustUnderstand="0">
         <ns1:CAISOUsernameToken>
            <ns1:Username>CN=XXXX, OU=people, O=XXXX, C=US</ns1:Username>
            <Nonce xmlns:ns2="http://schemas.xmlsoap.org/ws/2002/07/secext" EncodingType="http://docs.oasisopen.org/wss/2004/01/oasis-200401wss-soap-message-security-1.0#Base64Binary">MGNkNmU5ODMtZjcyZC00YTAyLWE5NWMtM2Q5Y2RjMTEyNDA1</Nonce>
            <Created xmlns:ns3="http://schemas.xmlsoap.org/ws/2002/07/utility">2018-03-02T21:13:45.932Z</Created>
         </ns1:CAISOUsernameToken>
         <CAISOMessageInfo>
            <MessageID>06c3379d-f10e-45f2-84eb-9262acce277e</MessageID>
            <Timestamp>
               <Created xmlns:ns4="http://schemas.xmlsoap.org/ws/2002/07/utility">2018-03-02T21:13:45.932Z</Created>
               <Expires xmlns:ns5="http://schemas.xmlsoap.org/ws/2002/07/utility">2018-03-02T23:13:45.932Z</Expires>
            </Timestamp>
         </CAISOMessageInfo>
      </ns1:CAISOWSHeader>
      <attachmentHash xmlns:ns7="http://www.caiso.com/mrtu/soa/schemas/2005/09/attachmenthash" actor="http://schemas.xmlsoap.org/soap/actor/next" mustUnderstand="0">
         <hashValue />
      </attachmentHash>
      <standardAttachmentInfor xmlns:ns1="http://www.caiso.com/soa/2006-06-13/StandardAttachmentInfor.xsd">
         <Attachment>
            <id>1</id>
            <compressFlag>yes</compressFlag>
            <compressMethod>gzip</compressMethod>
         </Attachment>
      </standardAttachmentInfor>
   </soapenv:Header>
   <soapenv:Body>
      <retrieveConvergenceBidAwards_v2>
         <RequestConvergenceBidAwards>
            <MessagePayload>
               <RequestConvergenceBidAwardRecord>
                  <dateTimeEnd>2018-02-02T08:00:00Z</dateTimeEnd>
                  <dateTimeStart>2018-02-01T08:00:00Z</dateTimeStart>
                  <SchedulingCoordinatorList>
                     <schedulingCoordinator>XXXX</schedulingCoordinator>
                  </SchedulingCoordinatorList>
               </RequestConvergenceBidAwardRecord>
            </MessagePayload>
         </RequestConvergenceBidAwards>
      </retrieveConvergenceBidAwards_v2>
   </soapenv:Body>
</soapenv:Envelope>

I have no idea where the issue is. Is it during the signing process, during the conversion from Document to String or in the way I perform the HTTP request?

Help is greatly appreciated :)

Vincent L
  • 699
  • 2
  • 11
  • 25
  • What is the servers WS-Policy? – xtratic Mar 02 '18 at 21:48
  • I don't know where I can find that out. Should it be included in the WSDL? I want to reproduce the same behavior that I am getting in SoapUI. – Vincent L Mar 02 '18 at 21:58
  • Yes, it should' be in the WSDL. The security policy needs to be specified somehow. – xtratic Mar 02 '18 at 22:11
  • I don't see anything about any policy in the WSDL, maybe I don't know what I'm looking for. I found a PDF document published by the host mentioning a lot of details about security specifications and I feel like I have followed everything in there. In fact, it works when I do it in SoapUI, so why shouldn't it if I follow the same specs? What is the difference between what I do in Java and SoapUI? – Vincent L Mar 03 '18 at 00:32
  • I could not link directly to it on the host because a certificate is needed to download the PDF, but I uploaded the PDF I just mentioned here: http://s000.tinyupload.com/?file_id=00672829768496931944 – Vincent L Mar 03 '18 at 00:47
  • Also run java with the JVM argument -Djavax.net.debug=all and look at the output when you are sending your request. Compare that output to what SoapUI is sending. – xtratic Mar 03 '18 at 20:48
  • I did look at the output and compared with what SoapUI is sending. I don't see any difference except the IDs and the signature value (which is normal since it's always different on every call). Even the Binary Token is exactly the same. – Vincent L Mar 04 '18 at 02:35
  • If you are sure that the output is exactly the same from both SoapUI and Java other than the signatures and timestamps then it is likely that the signatures that are wrong. Especially if it's telling you `ref[#id-32e3db92-b6fd-42a5-b032-a0dc2a15ae82] validity status: false` `ref[#id-2b67ce75-e25f-4f66-b265-80a0e31911ec] validity status: false` – xtratic Mar 05 '18 at 14:07
  • Even the digests are the same between Java and SoapUI output right? – xtratic Mar 05 '18 at 14:22

1 Answers1

8

I know this is an old question but I was struggling with this and I finally found a way to generate the signature like Soap UI does this is my code:

public String handleMessage(SOAPMessage message) {
        String ret;
        try {
            Document doc = message.getSOAPBody().getOwnerDocument();
            Crypto crypto = CryptoFactory.getInstance(properties); //File

            WSSecSignature sign = new WSSecSignature();
            sign.setUserInfo(properties.getProperty("org.apache.ws.security.crypto.merlin.keystore.alias"), properties.getProperty("privatekeypassword"));
            sign.setKeyIdentifierType(WSConstants.BST_DIRECT_REFERENCE); // Binary Security Token - SecurityTokenReference
            sign.setUseSingleCertificate(true);
            sign.setDigestAlgo(DigestMethod.SHA256);

            WSSecHeader secHeader = new WSSecHeader();
            secHeader.insertSecurityHeader(doc);
            Document signedDoc = sign.build(doc, crypto, secHeader);

            ret = org.apache.ws.security.util.XMLUtils.PrettyDocumentToString(signedDoc);

        } catch (SOAPException e) {
            e.printStackTrace();
            return null;
        } catch (WSSecurityException e) {
            e.printStackTrace();
            throw new RuntimeException("Error: " + e.getMessage());
        }
        return ret;
    }

This code was take from: https://sourceforge.net/p/signsoaprequest/code/HEAD/tree/SignSOAPRequest/trunk/src/main/java/br/gov/dataprev/soaptools/sign/

Julian Mesa
  • 379
  • 3
  • 4
  • You are a master. Your "raw" solution (I mean: using just SOAPConnection, without CFX, Axis, ...) to reproduce the SoapUI WS-Security Configuration works fine and it has saved me time. Many thanks for your "proactivity" to add your solution at your questions :) – Gabriele Dec 08 '19 at 15:21
  • how can I sign only a part of the soap message. I want to sign multiple parts inside a soap message. any help would be greatful! – Sai Ram Reddy Apr 06 '20 at 07:39
  • I am also looking for this but, I'm using axis2 to generate the client from wsdl. The thing is, I have a SOAPEnvelope object and MessageContext. Any ideas how to add? – Alejandro L. Aug 21 '20 at 08:13