javaweb-servicessoapsoapuiwss4j

WSSE - Sign an element inside soapenv:Header


I want to add wsse:security to my soap message. This is my code:

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

        WSSecHeader secHeader = new WSSecHeader(doc);
        secHeader.insertSecurityHeader();

        InputStream inStream = new FileInputStream(properties.getProperty("org.apache.ws.security.crypto.merlin.keystore.file"));

        KeyStore ks = KeyStore.getInstance("PKCS12");
        ks.load(inStream, properties.getProperty("privatekeypassword").toCharArray());

        String alias = ks.aliases().nextElement();
        X509Certificate cert = (X509Certificate) ks.getCertificate(alias);

        WSSecSignature sign = new WSSecSignature(secHeader);
        sign.setX509Certificate(cert);
        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.SHA1);

        //sign.build(crypto);
        Document signedDoc = sign.build(crypto);

        return signedDoc;
    } catch (SOAPException e) {
        e.printStackTrace();
        return null;
    } catch (WSSecurityException e) {
        e.printStackTrace();
        throw new RuntimeException("Error: " + e.getMessage());
    } catch (IOException e) {
        e.printStackTrace();
        return null;
    } catch (CertificateException e) {
        e.printStackTrace();
        return null;
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
        return null;
    } catch (KeyStoreException e) {
        e.printStackTrace();
        return null;
    }
}

It works for soapenv:Body (It does add wsu:Id and xmlns:wsu parameters)

But there is an extra element in soapenv:Header and it doesn't sign this element. There is no wsu:Id and xmlns:wsu parameters and lack of one ds:Reference.

Example of not signed soap msg:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
   <soapenv:Header>
    <!-- this element should be signed but is not - NOT WORKING -->
    <something>
    </something>

   </soapenv:Header>
   <!-- this element should be signed and It does. -->
   <soapenv:Body>
    </soapenv:Body>
</soapenv:Envelope>

I compare soap msg from my program to working soap msg from SoapUI project.

When I post a message to web service I get an error: wsse:InvalidSecurity - Soap Header must be signed. While in SoupUI it does works.

So my question is how can I force WSS4j to sign extra element inside soapenv:Header ?


Solution

  • Ok, I have solved the problem.

    Normally this code should work in my situation.

    //strange static method from apache o.O
    org.apache.xml.security.Init.init();
    List<WSEncryptionPart> wsEncryptionParts = new ArrayList<>();
    WSEncryptionPart somethingPart = new WSEncryptionPart("something","somethingNamespace","");
    wsEncryptionParts.add(somethingPart);
    sign.addReferencesToSign(wsEncryptionParts);
    

    Nevertheless, It doesn't work. It always throws an exception:

    org.apache.wss4j.common.ext.WSSecurityException: No message with ID "noXMLSig" found in resource bundle "org/apache/xml/security/resource/xmlsecurity". Original Exception was a org.apache.wss4j.common.ext.WSSecurityException and message No message with ID "noEncElement" found in resource bundle "org/apache/xml/security/resource/xmlsecurity"

    I could not find an answer to what is wrong with my soap message or code.

    However, after a time of debugging of org.apache.wss4j.dom.message.WSSecSignature. I felt that something is wrong with the class. I decided to modify a method build(Crypto cr).

    public Document build(Crypto cr) throws WSSecurityException {
            LOG.debug("Beginning signing...");
            this.prepare(cr);
            if (this.getParts().isEmpty()) {
                this.getParts().add(WSSecurityUtil.getDefaultEncryptionPart(this.getDocument()));
    
                // --- Here is my edit - And it works!
    
                WSEncryptionPart aaa = new WSEncryptionPart("something","somethingNamespace","");
                this.getParts().add(aaa);
    
                // ----------------------------------
    
            } else {
                Iterator var2 = this.getParts().iterator();
    
                label33:
                while(true) {
                    while(true) {
                        if (!var2.hasNext()) {
                            break label33;
                        }
    
                        WSEncryptionPart part = (WSEncryptionPart)var2.next();
                        if (part.getId() == null && "STRTransform".equals(part.getName())) {
                            part.setId(this.strUri);
                        } else if ("KeyInfo".equals(part.getName()) && "http://www.w3.org/2000/09/xmldsig#".equals(part.getNamespace()) && part.getElement() == null) {
                            Element keyInfoElement = this.getKeyInfoElement();
                            part.setElement(keyInfoElement);
                        }
                    }
                }
            }
    
            List<javax.xml.crypto.dsig.Reference> referenceList = this.addReferencesToSign(this.getParts());
            this.computeSignature(referenceList);
            if (this.bstToken != null) {
                this.prependBSTElementToHeader();
            }
    
            return this.getDocument();
        }
    

    Of course, the solution is quite weak. Yet, at least it works now.

    The problem exists in the newest version:

    wss4j-ws-security-dom 2.2.2

    wss4j-ws-security-common 2.2.2