Menu

Home

Inania Verba

As part of a project that I have been worked, I needed to sign a SOAP request using a digital certificate. I not found any way to do that using only SilkPerformer language in a bdl script, in a simple SOAP/HTML project, then I built a java program with only one responsability: sign a SOAP request message using a given digital certificate. In the SilkPerformer project was very easy to use the jar file and call the method of a java class that receive the original text and responds with it signed.

Given one SOAP request message, like that:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:jws="http://your.application.namespace.com/">
   <soapenv:Header/>
   <soapenv:Body>
      <jws:RequestObject>
        <FirstRequestParam>value_for_first_request_param</FirstRequestParam>
        <SecondRequestParam>value_for_second_request_param</SecondRequestParam>
      </jws:RequestObject>
   </soapenv:Body>
</soapenv:Envelope>

I would like to sign it with one digital certificate before post it to the target webservice. After signed, the message will look like that:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:jws="http://your.application.namespace.com/">
   <soapenv:Header><wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" soapenv:mustUnderstand="1"><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="X509-8E117EDA0C821E476214480337624021">MIIEpzCCAo+gAwIBAgIIXTtGg76z1zQwDQYJKoZIhvcNAQEFBQAwNTETMBEGA1UEAwwKRGF0YXByZXZDQTERMA8GA1UECgwIRGF0YXByZXYxCzAJBgNVBAYTAkJSMB4XDTE0MDU...</wsse:BinarySecurityToken><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="SIG-2"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"><ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="jws soapenv"/></ds:CanonicalizationMethod><ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><ds:Reference URI="#id-1"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"><ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="jws"/></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>e/d4O/ccla89rE6tQDXfl4A/D46SspEfY0UjFxTUyWI=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>hBxthOGIw1lUZWvpKMZMdralQ/WQCUTAa3BgnhrRgAICv473q94rVeSrMD8uamOTg6WYukpJhl0t...</ds:SignatureValue><ds:KeyInfo Id="KI-8E117EDA0C821E476214480337624112"><wsse:SecurityTokenReference wsu:Id="STR-8E117EDA0C821E476214480337624133"><wsse:Reference URI="#X509-8E117EDA0C821E476214480337624021" 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:Security></soapenv:Header>
   <soapenv:Body xmlns:soapsec="http://schemas.xmlsoap.org/soap/security/2000-12" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" soapsec:id="Body" wsu:Id="id-1">
      <jws:RequestObject>
        <FirstRequestParam>value_for_first_request_param</FirstRequestParam>
        <SecondRequestParam>value_for_second_request_param</SecondRequestParam>
      </jws:RequestObject>
   </soapenv:Body>
</soapenv:Envelope>

Obs.: Because it´s too long, the text of the tags “BinarySecurityToken” and “SignatureValue” was truncated.

To obtain the signed message, I built a java class that has a method to sign one SOAPMessage object using a known digital certificate, provided by a JKS keystore file. This class was named by WSSecurityHandler.

One properties file will provide other details needed, as decribed below:

org.apache.ws.security.crypto.merlin.keystore.file=[jks flie name, with extension]
org.apache.ws.security.crypto.merlin.keystore.password=[password for this jks file]
org.apache.ws.security.crypto.merlin.keystore.type=[type of keystore, that is this sample, jks]
privatekeypassword=[private key password recorded in the jks file]
org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.keystore.alias=[alias to the keystore]

In this study case, I was only using Merlin crypto provider.

The method that I used to recovery the properties was:

protected Properties loadConfig() {
    Properties prop = new Properties();
    try {
        prop.load(new FileInputStream(this.resourceNameConfigurations));
    } catch (IOException ex) {
        ex.printStackTrace();
    }
    return prop;
}

The attribute resourceNameConfigurations is the full path to the properties file, that can be set by the constructor of the WSSecurityHandler class.

The another method of the WSSecurityHandler class has the responsability to sign:

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

        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); 
        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;
}

Some aspects may be more configurable, like the digest method algorithm, but, let them in this way for now.

If you do not have the SOAPMessage object, but the text string of the message instead, the code below is enough to build it:

InputStream file = new ByteArrayInputStream(originalTextRequestMessage.getBytes());
// or, from file: InputStream file = new FileInputStream(fullPathToTheRequestMessageFile);

SOAPMessage soapMessage = MessageFactory.newInstance().createMessage(null, file);

SOAPPart soapPart = soapMessage.getSOAPPart();
SOAPEnvelope soapEnvelope = soapPart.getEnvelope();

Name name = soapEnvelope.createName("id", "soapsec",
                "http://schemas.xmlsoap.org/soap/security/2000-12");

SOAPBody soapBody = soapEnvelope.getBody();
soapBody.addAttribute(name, "Body");

WSSecurityHandler handler = new WSSecurityHandler();

handler.handleMessage(soapMessage);

It´s all folks!

This simple code project can be founded at:

https://sourceforge.net/projects/signsoaprequest/

The post that used this approach in a SilkPerformer SOAP/HTML project can be founded at:

[SilkPerformer_usage]

Project Members: