Skip to content

Commit

Permalink
org.apache.wss4j.dom.transform.STRTransform broken in WSS4j 3.0.0 qua…
Browse files Browse the repository at this point in the history
  • Loading branch information
ppalaga committed Jul 13, 2023
1 parent da2e501 commit e807c05
Show file tree
Hide file tree
Showing 11 changed files with 277 additions and 0 deletions.
31 changes: 31 additions & 0 deletions bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,37 @@
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>org.apache.wss4j</groupId>
<artifactId>wss4j-bindings</artifactId>
<version>${wss4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.wss4j</groupId>
<artifactId>wss4j-policy</artifactId>
<version>${wss4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.wss4j</groupId>
<artifactId>wss4j-ws-security-common</artifactId>
<version>${wss4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.wss4j</groupId>
<artifactId>wss4j-ws-security-dom</artifactId>
<version>${wss4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.wss4j</groupId>
<artifactId>wss4j-ws-security-policy-stax</artifactId>
<version>${wss4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.wss4j</groupId>
<artifactId>wss4j-ws-security-stax</artifactId>
<version>${wss4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.neethi</groupId>
<artifactId>neethi</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ quarkus.cxf.client."wss-client".out-interceptors = org.apache.cxf.ws.security.ws
# end::quarkus-cxf-rt-ws-security.adoc[]

quarkus.cxf.codegen.wsdl2java.includes = wsdl/dir/*.wsdl
quarkus.native.resources.includes = saml-keystore.jks
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ void reflectiveClass(CombinedIndexBuildItem combinedIndexBuildItem,
.map(className -> ReflectiveClassBuildItem.builder(className).build())
.forEach(reflectiveClass::produce);

reflectiveClass.produce(ReflectiveClassBuildItem.builder(org.apache.wss4j.dom.transform.STRTransform.class).build());
}

@BuildStep
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package io.quarkiverse.cxf.it.wss.client;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Properties;

import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;

import org.apache.wss4j.common.WSS4JConstants;
import org.apache.wss4j.common.bsp.BSPEnforcer;
import org.apache.wss4j.common.crypto.Crypto;
import org.apache.wss4j.common.crypto.CryptoFactory;
import org.apache.wss4j.common.ext.WSSecurityException;
import org.apache.wss4j.dom.engine.WSSConfig;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

@Path("/cxf/signature-validator")
public class SignatureValidatorResource {

private static final String TRUSTSTORE_RESOURCE = "saml-keystore.jks";

public final BSPEnforcer enforcer;
public final WSSConfig wssConfig;
public final Crypto crypto;

public SignatureValidatorResource() {
java.nio.file.Path trustStoreFile = null;
try (InputStream in = getClass().getClassLoader().getResourceAsStream(TRUSTSTORE_RESOURCE)) {
trustStoreFile = Files.createTempFile("saml-keystore-", ".jks");
Files.createDirectories(trustStoreFile.getParent());
Files.copy(in, trustStoreFile, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
throw new RuntimeException(
"Could not copy the classpath resource " + TRUSTSTORE_RESOURCE + " to " + trustStoreFile, e);
}

Properties properties = new Properties();
properties.setProperty("org.apache.ws.security.crypto.provider", "org.apache.ws.security.components.crypto.Merlin");
properties.put("org.apache.ws.security.crypto.merlin.truststore.type", "JKS");
properties.put("org.apache.ws.security.crypto.merlin.truststore.password", "changeit");
properties.put("org.apache.ws.security.crypto.merlin.truststore.file", trustStoreFile.toString());
properties.put("org.apache.ws.security.crypto.merlin.load.cacerts", "false");

this.enforcer = new BSPEnforcer();
this.wssConfig = WSSConfig.getNewInstance();
try {
this.crypto = CryptoFactory.getInstance(properties);
} catch (WSSecurityException e) {
throw new RuntimeException("Could not create Crypto", e);
}
}

@POST
@Path("/validate")
@Produces(MediaType.TEXT_PLAIN)
public boolean validate(@QueryParam("samlNamespace") String samlNamespace, InputStream body) throws Exception {

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document bodyDocument = builder.parse(body);

Element element = bodyDocument.getDocumentElement();
NodeList nodes = element.getElementsByTagNameNS(WSS4JConstants.WSSE_NS, WSS4JConstants.WSSE_LN);
Element wsseHeader = (Element) nodes.item(0);
if (wsseHeader == null) {
throw new SecurityException("Assertion missing in request!");
}

NodeList timestampNodes = wsseHeader.getElementsByTagNameNS(WSS4JConstants.WSU_NS,
WSS4JConstants.TIMESTAMP_TOKEN_LN);
NodeList samlNodes = wsseHeader.getElementsByTagNameNS(samlNamespace, WSS4JConstants.ASSERTION_LN);
NodeList binaryNodes = wsseHeader.getElementsByTagNameNS(WSS4JConstants.WSSE_NS, WSS4JConstants.BINARY_TOKEN_LN);
NodeList signatureNodes = wsseHeader.getElementsByTagNameNS(XMLSignature.XMLNS, WSS4JConstants.SIG_LN);
NodeList tokenRefs = wsseHeader.getElementsByTagNameNS(WSS4JConstants.WSSE_NS, "SecurityTokenReference");
if (isEmpty(timestampNodes, samlNodes, binaryNodes, signatureNodes)) {
throw new SecurityException("Assertion missing in request!");
}

return validateSecurityHeader(
timestampNodes.item(0),
samlNodes.item(0),
binaryNodes.item(0),
signatureNodes.item(0),
toList(tokenRefs));
}

private List<Node> toList(NodeList nodeList) {
final int cnt = nodeList.getLength();
if (cnt == 0) {
return Collections.emptyList();
}
final List<Node> res = new ArrayList<>(cnt);
for (int i = 0; i < cnt; i++) {
res.add(nodeList.item(i));
}
return res;
}

private boolean isEmpty(NodeList... nodeLists) {
for (NodeList nl : nodeLists) {
if (nl.getLength() == 0) {
return true;
}
}
return false;
}

private boolean validateSecurityHeader(
Node timestampNode,
Node samlNode,
Node binaryNode,
Node signatureNode, List<Node> additionalSignedNodes) throws Exception {

InputStream in = new ByteArrayInputStream(Base64.getMimeDecoder().decode(binaryNode.getTextContent()));
X509Certificate certificate = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(in);

List<Node> signedNodes = new ArrayList<>();
signedNodes.add(timestampNode);
signedNodes.add(samlNode);
signedNodes.addAll(additionalSignedNodes);

return validateSignature(certificate, signatureNode, signedNodes);
}

private boolean validateSignature(
X509Certificate certificate,
Node signatureNode,
List<Node> additionalNodes) throws Exception {

DOMValidateContext valContext = new DOMValidateContext(certificate.getPublicKey(), signatureNode);

// disable secure validation since we use SHA-1 algorithm for signature
valContext.setProperty("org.jcp.xml.dsig.secureValidation", Boolean.FALSE);
for (Node node : additionalNodes) {
Element el = (Element) node;
if (el.hasAttributeNS(WSS4JConstants.WSU_NS, "Id")) {
valContext.setIdAttributeNS(el, WSS4JConstants.WSU_NS, "Id");
} else if (el.hasAttribute("ID")) {
valContext.setIdAttributeNS(el, null, "ID");
}
}

XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
XMLSignature signature = fac.unmarshalXMLSignature(valContext);
return signature.validate(valContext);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ quarkus.cxf.client."wss-client".out-interceptors = org.apache.cxf.ws.security.ws
# end::quarkus-cxf-rt-ws-security.adoc[]

quarkus.cxf.codegen.wsdl2java.includes = wsdl/dir/*.wsdl
quarkus.native.resources.includes = saml-keystore.jks
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.quarkiverse.cxf.it.wss.client;

import io.quarkus.test.junit.QuarkusIntegrationTest;

@QuarkusIntegrationTest
public class SignatureValidatorIT extends SignatureValidatorTest {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.quarkiverse.cxf.it.wss.client;

import java.io.IOException;
import java.io.InputStream;

import org.apache.wss4j.common.WSS4JConstants;
import org.hamcrest.CoreMatchers;
import org.junit.jupiter.api.Test;

import io.quarkus.test.junit.QuarkusTest;
import io.restassured.RestAssured;

@QuarkusTest
public class SignatureValidatorTest {

@Test
public void saml1() throws Exception {
assertSaml("data/saml1-request.xml", WSS4JConstants.SAML_NS);
}

@Test
public void saml2() throws Exception {
assertSaml("data/saml2-request.xml", WSS4JConstants.SAML2_NS);
}

static void assertSaml(String message, String samlNamespace) throws IOException {
try (InputStream in = SignatureValidatorTest.class.getClassLoader().getResourceAsStream(message)) {
RestAssured.given()
.body(in)
.queryParam("samlNamespace", samlNamespace)
.post("/cxf/signature-validator/validate")
.then()
.statusCode(200)
.body(CoreMatchers.is("true"));
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?><soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/"><soap-env:Header xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"><wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsu:Timestamp wsu:Id="ts-005056867EF41EDBA0823B711B22C0F1"><wsu:Created>2021-03-08T13:26:20Z</wsu:Created><wsu:Expires>2021-03-08T13:27:50Z</wsu:Expires></wsu:Timestamp><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="bst-005056867EF41EDBA0823B711B23">MIIEnzCCAoegAwIBAgICAZkwDQYJKoZIhvcNAQELBQAwgagxKDAmBgkqhkiG9w0BCQEWGWljYy5pbmZvcm1hdGlrQGhlbHNhbmEuY2gxFzAVBgNVBAMMDnNvYXBfZGV2ZWxvcGVyMQwwCgYDVQQLDANJWEkxIjAgBgNVBAoMGUhlbHNhbmEgVmVyc2ljaGVydW5nZW4gQUcxEzARBgNVBAcMCkR1ZWJlbmRvcmYxDzANBgNVBAgMBlp1cmljaDELMAkGA1UEBhMCQ0gwHhcNNzAwMTAxMDAwMDE5WhcNNDMwMzEwMDAwMDAwWjB8MSIwIAYDVQQDDBllbXBsb3llZTItYXNlbS5oZWwua2tvLmNoMRMwEQYDVQQLDApJVCBTZXJ2aWNlMSIwIAYDVQQKDBlIZWxzYW5hIFZlcnNpY2hlcnVuZ2VuIEFHMRAwDgYDVQQHDAdadWVyaWNoMQswCQYDVQQGEwJDSDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJpYavV1KEVUw7X507gDz9L1JeIqv0CZvkcodnr4dByD3DnWSqI7Af/jwsnmDGRbG0WdTmPt/5CP5UWY5CtnMvDZ4MbTEE88v1hZ4BjbS/ecCdHhZAe97SHXXAs+DPNvE5Q7fFtE70no+DsAhoY9cnvIJTuVwve7RgQY4ggQGCPvpHcIeRh8atE9FTZi64oHcK8Gsa/7ewj8gOGe2fosyLnoeUnyYJwNroTbOqDm8tLhxcDM4AbIxekbcemF2ENf8B1s8+QvOg//St6Dy0eUIWNw+vhNdCaidYk/ADTxLJ2Ps4BXjNK7/vifGkssQ+xNsYYr7+Mkx1tAOx2h9Pe7k28CAwEAATANBgkqhkiG9w0BAQsFAAOCAgEApIJSdLyVv8m4AozIDCtL0im5BO0eGyV8NI0WsETAlbdHMO0nzjtORzNaid+SWOtYHRzIkR4U+8Xk0/4wU3QmP4I6fJG65yugpxeAQhC5Txu2Wm/9dbJy3lU9cSQFEF4ne9V0rX75IwCZ5XJVYOrZlUkFj8JpFiVuV03DxvqLvDlE4CMcLhIo/AV7ACTnJU/a+XzMHh9ME0+QX8h+MWTaUqH3x9fLK69FOF+E2i0om50Ib9K8fxoTAzvzzSxZW9iF3XNAyKHDkBo1EfgmgjfK7ck/kqzMfml5RQXFaYVLBbMehr/CmYffsCjSpvB0LXkQznjmma7MhJMGepmztGDBkXC2YZjXqzu42E2GecEP5v3l6k5AEyekCcKQM0Mh/vWMb9BCgGe52i9ZegRV1l4MxkHdOc0yOKRHJSQsJCftmUllZDWr20pMZ2k485zBYa/BypJXNMh3CaQlztSEIEKr+ohY5KwemPRJhEDI1eCcd3IZz62p8AuVPRPrYXOlVz2F7mYY+qgS5CLBD3B/U40lHmxKbF4uIfYDfluHVyk8mtAqnGmYBBWchpCZUffYuW/1kExHsMBoDjK5jNtTlK/k64xk78aMksk/P5NnXpfjaJujXoFYJIWv3hdOS8lKCwLDg1mrpmQ3UmGOZqG9vVkM+lNQPl5PrDpYJ8t8XoX3sM8=</wsse:BinarySecurityToken><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="sig-005056867EF41EDBA0823B711B22E0F1"><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="#ts-005056867EF41EDBA0823B711B22C0F1"><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>XEbLqMXooIX5KzNGxXuTSodawyE=</ds:DigestValue></ds:Reference><ds:Reference URI="#str-005056867EF41EDBA0823B711B2300F1"><ds:Transforms><ds:Transform Algorithm="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#STR-Transform"><wsse:TransformationParameters><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></wsse:TransformationParameters></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ds:DigestValue>06wG7NolfMKiAQj8LFtYs+AYr28=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>PrTNKPN0ANd/rjrDr982VqHN023g7pElNoMjH7ueu1wwpGVIvbk4YnKz60SBlbvi6dhTL8kSqDPk
M1gCZnf8Q4Py6j2mrstMr51rWr4p2aSASQIRRVO6/CwScM28SJuZAyZnuplvh+d5vBQt+sRhEiul
cYtraaP/QbFlhKZtqY/CbKA91pzOunzwKVEwGvmEW1BWQUFxjA86ABZcQ1Obe2moSfPLQ3V+VKTA
7/IonAOMPufNUEoy57V4abO0wzPldEySpT6Gxs///t60JOuJ+A2FAN+ZmTjEgN2a+uj++iDL0XEo
yam6pukBuNgv9wjZgiKkpw3XTo3AqHvZwVc29Q==</ds:SignatureValue><ds:KeyInfo><wsse:SecurityTokenReference><wsse:Reference URI="#bst-005056867EF41EDBA0823B711B23" 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:SecurityTokenReference wsu:Id="str-005056867EF41EDBA0823B711B2300F1"><wsse:KeyIdentifier ValueType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID">saml-005056867EF41EDBA0823B711B22A0F1</wsse:KeyIdentifier></wsse:SecurityTokenReference><saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" AssertionID="saml-005056867EF41EDBA0823B711B22A0F1" IssueInstant="2021-03-08T13:26:20Z" Issuer="http://services.sap-t22.hel.kko.ch" MajorVersion="1" MinorVersion="1"><saml:Conditions NotBefore="2021-03-08T13:26:20Z" NotOnOrAfter="2021-03-08T13:31:20Z"/><saml:AuthenticationStatement AuthenticationInstant="2021-03-08T13:26:20Z" AuthenticationMethod="urn:oasis:names:tc:SAML:1.0:am:unspecified"><saml:Subject><saml:NameIdentifier Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" NameQualifier="">HKE32</saml:NameIdentifier><saml:SubjectConfirmation><saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:sender-vouches</saml:ConfirmationMethod></saml:SubjectConfirmation></saml:Subject></saml:AuthenticationStatement></saml:Assertion></wsse:Security><sap:Trace xmlns:sap="http://www.sap.com/webas/630/soap/features/runtime/tracing/" wsu:Id="part-Trace-5"><sap:TraceLevel>Payload</sap:TraceLevel><sap:TraceContext><TRC_PATTERN>WSTEST</TRC_PATTERN><TRC_KEY>031180EB5E99F1A380F1005056867EF4</TRC_KEY><TRC_ROOT_CTX_ID/><TRC_SSID>T22_01</TRC_SSID><TRC_USER>HKE32</TRC_USER><TRC_TS>20210308132603</TRC_TS><TRC_COUNTER>98</TRC_COUNTER><TRC_EXTERN/><TRC_REQBASED/><TRC_PPVERS>3</TRC_PPVERS></sap:TraceContext></sap:Trace><m:CallerInformation xmlns:m="http://www.sap.com/webas/712/soap/features/runtime/metering/" wsu:Id="part-CallerInformation-6"><m:Type>SA</m:Type><m:Company>0020203013</m:Company><m:Sys>T22_010</m:Sys></m:CallerInformation></soap-env:Header><soap-env:Body xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="part-Body-7"><nm:processPaymentInstrument xmlns:nm="http://www.adcubum.com/wsdl/syrius.modul_bl.payment.global.v04_1_0.rechtlichesinkasso.service/GlobalRIKMeldungImporter" xmlns:prx="urn:sap.com:proxy:P11:/1SAI/TAS4A079764B0183C3E95BD:731"><n0:data xmlns:n0="http://www.adcubum.com/wsdl/syrius.modul_bl.payment.global.v04_1_0.rechtlichesinkasso.service/GlobalRIKMeldungImporter">&lt;?xml version="1.0" encoding="utf-8"?&gt;&lt;envelopeType&gt;&lt;meta&gt;&lt;messageId&gt;051MXdxq7jkWWZjn6o8WyG&lt;/messageId&gt;&lt;origin&gt;osytt22-T54-220&lt;/origin&gt;&lt;time&gt;2021-03-08T14:26:20.591000&lt;/time&gt;&lt;/meta&gt;&lt;message&gt;&lt;paymentInstrumentType&gt;&lt;esrReferenceNumber&gt;006839929000016503709010119&lt;/esrReferenceNumber&gt;&lt;dossId&gt;0006839929&lt;/dossId&gt;&lt;/paymentInstrumentType&gt;&lt;/message&gt;&lt;/envelopeType&gt;</n0:data></nm:processPaymentInstrument></soap-env:Body></soap-env:Envelope>

0 comments on commit e807c05

Please sign in to comment.