Skip to content

Commit 46447de

Browse files
author
Andreas Winter
committed
Enable sign with cert chain and configuration of subjectDnConstraints
1 parent 5e73b4c commit 46447de

File tree

3 files changed

+78
-0
lines changed

3 files changed

+78
-0
lines changed

spring-ws-security/src/main/java/org/springframework/ws/soap/security/wss4j2/Wss4jSecurityInterceptor.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@
2020
import java.security.Principal;
2121
import java.security.cert.X509Certificate;
2222
import java.util.ArrayList;
23+
import java.util.Arrays;
2324
import java.util.Collections;
2425
import java.util.List;
26+
import java.util.regex.Pattern;
27+
import java.util.stream.Collectors;
2528

2629
import javax.security.auth.callback.Callback;
2730
import javax.security.auth.callback.CallbackHandler;
@@ -59,6 +62,9 @@
5962
import org.w3c.dom.Document;
6063
import org.w3c.dom.Element;
6164

65+
import static java.util.Collections.emptyList;
66+
import static java.util.Collections.unmodifiableList;
67+
6268
/**
6369
* A WS-Security endpoint interceptor based on Apache's WSS4J. This interceptor supports messages created by the
6470
* {@link org.springframework.ws.soap.axiom.AxiomSoapMessageFactory} and the
@@ -194,6 +200,8 @@ public class Wss4jSecurityInterceptor extends AbstractWsSecurityInterceptor impl
194200
// To maintain same behavior as default, this flag is set to true
195201
private boolean removeSecurityHeader = true;
196202

203+
private List<Pattern> signatureSubjectDnPatterns = emptyList();
204+
197205
/**
198206
* Create a {@link WSSecurityEngine} by default.
199207
*/
@@ -210,6 +218,20 @@ public Wss4jSecurityInterceptor(WSSecurityEngine securityEngine) {
210218
this.securityEngine = securityEngine;
211219
}
212220

221+
/**
222+
* Certificate constraints which will be applied to the subject DN of the certificate used for
223+
* signature validation, after trust verification of the certificate chain associated with the
224+
* certificate.
225+
*
226+
* @param patterns A comma separated String of regular expressions which will be applied to
227+
* the subject DN.
228+
*
229+
* @see <a href="https://ws.apache.org/wss4j/config.html">WSS4J configuration: SIG_SUBJECT_CERT_CONSTRAINTS</a>
230+
*/
231+
public void setValidationSubjectDnConstraints(List<Pattern> patterns) {
232+
signatureSubjectDnPatterns = patterns;
233+
}
234+
213235
public void setSecurementActions(String securementActions) {
214236
this.securementActions = securementActions;
215237
}
@@ -225,6 +247,15 @@ public void setSecurementActor(String securementActor) {
225247
handler.setOption(WSHandlerConstants.ACTOR, securementActor);
226248
}
227249

250+
/**
251+
* Defines whether to use a single certificate or a whole certificate chain when constructing
252+
* a BinarySecurityToken used for direct reference in signature.
253+
* The default is "true", meaning that only a single certificate is used.
254+
*/
255+
public void setSecurementSignatureSingleCertificate(boolean useSingleCertificate) {
256+
handler.setOption(WSHandlerConstants.USE_SINGLE_CERTIFICATE, useSingleCertificate);
257+
}
258+
228259
public void setSecurementEncryptionCrypto(Crypto securementEncryptionCrypto) {
229260
handler.setSecurementEncryptionCrypto(securementEncryptionCrypto);
230261
}
@@ -670,6 +701,7 @@ protected RequestData initializeRequestData(MessageContext messageContext) {
670701
// allow for qualified password types for .Net interoperability
671702
requestData.setAllowNamespaceQualifiedPasswordTypes(true);
672703

704+
requestData.setSubjectCertConstraints(signatureSubjectDnPatterns);
673705
return requestData;
674706
}
675707

@@ -710,6 +742,8 @@ protected RequestData initializeValidationRequestData(MessageContext messageCont
710742
// allow for qualified password types for .Net interoperability
711743
requestData.setAllowNamespaceQualifiedPasswordTypes(true);
712744

745+
requestData.setSubjectCertConstraints(signatureSubjectDnPatterns);
746+
713747
return requestData;
714748
}
715749

@@ -888,4 +922,14 @@ protected void cleanUp() {
888922
}
889923
}
890924
}
925+
926+
private List<Pattern> parseSubjectCertConstrainsAsPatternList() {
927+
String commaSeparatedCertConstraintPatterns = handler.getStringOption(ConfigurationConstants.SIG_SUBJECT_CERT_CONSTRAINTS);
928+
if (commaSeparatedCertConstraintPatterns != null && !commaSeparatedCertConstraintPatterns.isEmpty()) {
929+
String[] patternStrings = commaSeparatedCertConstraintPatterns.split(",");
930+
return unmodifiableList(Arrays.stream(patternStrings).map(Pattern::compile).collect(Collectors.toList()));
931+
} else {
932+
return emptyList();
933+
}
934+
}
891935
}

spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jMessageInterceptorSignTestCase.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818

1919
import static org.assertj.core.api.Assertions.*;
2020

21+
import java.util.List;
2122
import java.util.Properties;
23+
import java.util.regex.Pattern;
2224

2325
import org.junit.jupiter.api.Test;
2426
import org.springframework.ws.WebServiceMessage;
@@ -121,4 +123,36 @@ public void testSignResponseWithSignatureUser() throws Exception {
121123
assertXpathExists("Absent SignatureConfirmation element",
122124
"/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/ds:Signature", document);
123125
}
126+
127+
@Test
128+
public void testValidateCertificateSubjectDnConstraintsShouldMatchSubject() throws Exception {
129+
SoapMessage message = createSignedTestSoapMessage();
130+
MessageContext messageContext = getSoap11MessageContext(createSignedTestSoapMessage());
131+
interceptor.secureMessage(message, messageContext);
132+
133+
interceptor.setValidationActions("Signature");
134+
interceptor.setValidationSubjectDnConstraints(List.of(Pattern.compile(".*")));
135+
assertThatCode(() ->interceptor.validateMessage(message, messageContext)).doesNotThrowAnyException();
136+
}
137+
138+
@Test
139+
public void testValidateCertificateSubjectDnConstraintsShouldFailForNotMatchingSubject() throws Exception {
140+
SoapMessage message = createSignedTestSoapMessage();
141+
MessageContext messageContext = getSoap11MessageContext(createSignedTestSoapMessage());
142+
interceptor.secureMessage(message, messageContext);
143+
144+
interceptor.setValidationActions("Signature");
145+
interceptor.setValidationSubjectDnConstraints(List.of(Pattern.compile("O=Some Other Company")));
146+
Throwable catched = catchThrowable(() -> interceptor.validateMessage(message, messageContext));
147+
assertThat(catched).isInstanceOf(Wss4jSecurityValidationException.class);
148+
}
149+
150+
private SoapMessage createSignedTestSoapMessage() throws Exception {
151+
interceptor.setSecurementActions("Signature");
152+
interceptor.setSecurementSignatureKeyIdentifier("DirectReference");
153+
interceptor.setSecurementSignatureSingleCertificate(false);
154+
interceptor.setSecurementPassword("123456");
155+
interceptor.setSecurementUsername("testkey");
156+
return loadSoap11Message("empty-soap.xml");
157+
}
124158
}
5.28 KB
Binary file not shown.

0 commit comments

Comments
 (0)