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 3b457cf46..c04cfc58a 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
@@ -20,8 +20,11 @@
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
@@ -59,6 +62,9 @@
import org.w3c.dom.Document;
import org.w3c.dom.Element;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.unmodifiableList;
+
/**
* A WS-Security endpoint interceptor based on Apache's WSS4J. This interceptor supports messages created by the
* {@link org.springframework.ws.soap.axiom.AxiomSoapMessageFactory} and the
@@ -138,6 +144,7 @@
* @author Jamin Hitchcock
* @author Rob Leland
* @author Lars Uffmann
+ * @author Andreas Winter
* @see Apache WSS4J 2.0
* @since 2.3.0
*/
@@ -194,6 +201,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 = emptyList();
+
/**
* Create a {@link WSSecurityEngine} by default.
*/
@@ -225,6 +234,15 @@ public void setSecurementActor(String securementActor) {
handler.setOption(WSHandlerConstants.ACTOR, securementActor);
}
+ /**
+ * Defines whether to use a single certificate or a whole certificate chain when constructing
+ * a BinarySecurityToken used for direct reference in signature.
+ * The default is "true", meaning that only a single certificate is used.
+ */
+ public void setSecurementSignatureSingleCertificate(boolean useSingleCertificate) {
+ handler.setOption(WSHandlerConstants.USE_SINGLE_CERTIFICATE, useSingleCertificate);
+ }
+
public void setSecurementEncryptionCrypto(Crypto securementEncryptionCrypto) {
handler.setSecurementEncryptionCrypto(securementEncryptionCrypto);
}
@@ -485,6 +503,19 @@ public void setValidationSignatureCrypto(Crypto signatureCrypto) {
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 WSS4J configuration: SIG_SUBJECT_CERT_CONSTRAINTS
+ */
+ public void setValidationSubjectDnConstraints(List patterns) {
+ signatureSubjectDnPatterns = patterns;
+ }
+
/** Whether to enable signatureConfirmation or not. By default signatureConfirmation is enabled */
public void setEnableSignatureConfirmation(boolean enableSignatureConfirmation) {
@@ -670,6 +701,7 @@ protected RequestData initializeRequestData(MessageContext messageContext) {
// allow for qualified password types for .Net interoperability
requestData.setAllowNamespaceQualifiedPasswordTypes(true);
+ requestData.setSubjectCertConstraints(signatureSubjectDnPatterns);
return requestData;
}
@@ -710,6 +742,8 @@ protected RequestData initializeValidationRequestData(MessageContext messageCont
// allow for qualified password types for .Net interoperability
requestData.setAllowNamespaceQualifiedPasswordTypes(true);
+ requestData.setSubjectCertConstraints(signatureSubjectDnPatterns);
+
return requestData;
}
diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorSignTestCase.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorSignTestCase.java
index 0e8490af9..729221b73 100644
--- a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorSignTestCase.java
+++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorSignTestCase.java
@@ -18,7 +18,9 @@
import static org.assertj.core.api.Assertions.*;
+import java.util.List;
import java.util.Properties;
+import java.util.regex.Pattern;
import org.junit.jupiter.api.Test;
import org.springframework.ws.WebServiceMessage;
@@ -121,4 +123,36 @@ public void testSignResponseWithSignatureUser() throws Exception {
assertXpathExists("Absent SignatureConfirmation element",
"/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/ds:Signature", document);
}
+
+ @Test
+ public void testValidateCertificateSubjectDnConstraintsShouldMatchSubject() throws Exception {
+ SoapMessage message = createSignedTestSoapMessage();
+ MessageContext messageContext = getSoap11MessageContext(createSignedTestSoapMessage());
+ interceptor.secureMessage(message, messageContext);
+
+ interceptor.setValidationActions("Signature");
+ interceptor.setValidationSubjectDnConstraints(List.of(Pattern.compile(".*")));
+ assertThatCode(() ->interceptor.validateMessage(message, messageContext)).doesNotThrowAnyException();
+ }
+
+ @Test
+ public void testValidateCertificateSubjectDnConstraintsShouldFailForNotMatchingSubject() throws Exception {
+ SoapMessage message = createSignedTestSoapMessage();
+ MessageContext messageContext = getSoap11MessageContext(createSignedTestSoapMessage());
+ interceptor.secureMessage(message, messageContext);
+
+ interceptor.setValidationActions("Signature");
+ interceptor.setValidationSubjectDnConstraints(List.of(Pattern.compile("O=Some Other Company")));
+ Throwable catched = catchThrowable(() -> interceptor.validateMessage(message, messageContext));
+ assertThat(catched).isInstanceOf(Wss4jSecurityValidationException.class);
+ }
+
+ private SoapMessage createSignedTestSoapMessage() throws Exception {
+ interceptor.setSecurementActions("Signature");
+ interceptor.setSecurementSignatureKeyIdentifier("DirectReference");
+ interceptor.setSecurementSignatureSingleCertificate(false);
+ interceptor.setSecurementPassword("123456");
+ 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 b3b10e36f..15a4ebafe 100644
Binary files a/spring-ws-security/src/test/resources/private.jks and b/spring-ws-security/src/test/resources/private.jks differ