diff --git a/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/Wss4jSecurityInterceptor.java b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/Wss4jSecurityInterceptor.java index c965703d..83f5fc5a 100644 --- a/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/Wss4jSecurityInterceptor.java +++ b/spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/Wss4jSecurityInterceptor.java @@ -22,6 +22,7 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.regex.Pattern; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; @@ -208,6 +209,8 @@ public class Wss4jSecurityInterceptor extends AbstractWsSecurityInterceptor impl // To maintain same behavior as default, this flag is set to true private boolean removeSecurityHeader = true; + private List signatureSubjectDnPatterns = Collections.emptyList(); + /** * Create a {@link WSSecurityEngine} by default. */ @@ -527,6 +530,17 @@ public class Wss4jSecurityInterceptor extends AbstractWsSecurityInterceptor impl this.validationSignatureCrypto = signatureCrypto; } + /** + * Certificate constraints which will be applied to the subject DN of the certificate + * used for signature validation, after trust verification of the certificate chain + * associated with the certificate. + * @param patterns a list of regex patterns which will be applied to the subject DN. + * @see ConfigurationConstants#SIG_SUBJECT_CERT_CONSTRAINTS + */ + public void setValidationSubjectDnConstraints(List patterns) { + this.signatureSubjectDnPatterns = patterns; + } + /** * Whether to enable signatureConfirmation or not. By default, signatureConfirmation * is enabled. @@ -741,6 +755,7 @@ public class Wss4jSecurityInterceptor extends AbstractWsSecurityInterceptor impl // allow for qualified password types for .Net interoperability requestData.setAllowNamespaceQualifiedPasswordTypes(true); + requestData.setSubjectCertConstraints(this.signatureSubjectDnPatterns); return requestData; } @@ -780,6 +795,7 @@ public class Wss4jSecurityInterceptor extends AbstractWsSecurityInterceptor impl // allow for qualified password types for .Net interoperability requestData.setAllowNamespaceQualifiedPasswordTypes(true); + requestData.setSubjectCertConstraints(this.signatureSubjectDnPatterns); return requestData; } diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorSignTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorSignTest.java index 1765d054..f4c93506 100644 --- a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorSignTest.java +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorSignTest.java @@ -16,7 +16,9 @@ package org.springframework.ws.soap.security.wss4j2; +import java.util.List; import java.util.Properties; +import java.util.regex.Pattern; import org.junit.jupiter.api.Test; import org.w3c.dom.Document; @@ -28,6 +30,8 @@ import org.springframework.ws.soap.SoapMessage; import org.springframework.ws.soap.security.wss4j2.support.CryptoFactoryBean; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; public abstract class Wss4jMessageInterceptorSignTest extends Wss4jTest { @@ -123,4 +127,37 @@ public abstract class Wss4jMessageInterceptorSignTest extends Wss4jTest { "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/ds:Signature", document); } + @Test + public void testValidateCertificateSubjectDnConstraintsShouldMatchSubject() throws Exception { + SoapMessage message = createSignedTestSoapMessage(); + MessageContext messageContext = getSoap11MessageContext(createSignedTestSoapMessage()); + this.interceptor.secureMessage(message, messageContext); + + this.interceptor.setValidationActions("Signature"); + this.interceptor.setValidationSubjectDnConstraints(List.of(Pattern.compile(".*"))); + assertThatCode(() -> this.interceptor.validateMessage(message, messageContext)).doesNotThrowAnyException(); + } + + @Test + public void testValidateCertificateSubjectDnConstraintsShouldFailForNotMatchingSubject() throws Exception { + SoapMessage message = createSignedTestSoapMessage(); + MessageContext messageContext = getSoap11MessageContext(createSignedTestSoapMessage()); + this.interceptor.secureMessage(message, messageContext); + + this.interceptor.setValidationActions("Signature"); + this.interceptor.setValidationSubjectDnConstraints(List.of(Pattern.compile("O=Some Other Company"))); + assertThatExceptionOfType(Wss4jSecurityValidationException.class) + .isThrownBy(() -> this.interceptor.validateMessage(message, messageContext)) + .withMessage("The security token could not be authenticated or authorized"); + } + + private SoapMessage createSignedTestSoapMessage() throws Exception { + this.interceptor.setSecurementActions("Signature"); + this.interceptor.setSecurementSignatureKeyIdentifier("DirectReference"); + this.interceptor.setUseSingleCertificate(false); + this.interceptor.setSecurementPassword("123456"); + this.interceptor.setSecurementUsername("testkey"); + return loadSoap11Message("empty-soap.xml"); + } + } diff --git a/spring-ws-security/src/test/resources/private.jks b/spring-ws-security/src/test/resources/private.jks index b3b10e36..15a4ebaf 100644 Binary files a/spring-ws-security/src/test/resources/private.jks and b/spring-ws-security/src/test/resources/private.jks differ