Skip to content

Commit 1ec90a6

Browse files
author
Andreas Winter
committed
SWS-1058 - Enable sign with cert chain and configuration of subjectDnConstraints
1 parent 5e73b4c commit 1ec90a6

File tree

3 files changed

+68
-0
lines changed

3 files changed

+68
-0
lines changed

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

+34
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
@@ -138,6 +144,7 @@
138144
* @author Jamin Hitchcock
139145
* @author Rob Leland
140146
* @author Lars Uffmann
147+
* @author Andreas Winter
141148
* @see <a href="http://ws.apache.org/wss4j/">Apache WSS4J 2.0</a>
142149
* @since 2.3.0
143150
*/
@@ -194,6 +201,8 @@ public class Wss4jSecurityInterceptor extends AbstractWsSecurityInterceptor impl
194201
// To maintain same behavior as default, this flag is set to true
195202
private boolean removeSecurityHeader = true;
196203

204+
private List<Pattern> signatureSubjectDnPatterns = emptyList();
205+
197206
/**
198207
* Create a {@link WSSecurityEngine} by default.
199208
*/
@@ -225,6 +234,15 @@ public void setSecurementActor(String securementActor) {
225234
handler.setOption(WSHandlerConstants.ACTOR, securementActor);
226235
}
227236

237+
/**
238+
* Defines whether to use a single certificate or a whole certificate chain when constructing
239+
* a BinarySecurityToken used for direct reference in signature.
240+
* The default is "true", meaning that only a single certificate is used.
241+
*/
242+
public void setSecurementSignatureSingleCertificate(boolean useSingleCertificate) {
243+
handler.setOption(WSHandlerConstants.USE_SINGLE_CERTIFICATE, useSingleCertificate);
244+
}
245+
228246
public void setSecurementEncryptionCrypto(Crypto securementEncryptionCrypto) {
229247
handler.setSecurementEncryptionCrypto(securementEncryptionCrypto);
230248
}
@@ -485,6 +503,19 @@ public void setValidationSignatureCrypto(Crypto signatureCrypto) {
485503
this.validationSignatureCrypto = signatureCrypto;
486504
}
487505

506+
/**
507+
* Certificate constraints which will be applied to the subject DN of the certificate used for
508+
* signature validation, after trust verification of the certificate chain associated with the
509+
* certificate.
510+
*
511+
* @param patterns A list of regex patterns which will be applied to the subject DN.
512+
*
513+
* @see <a href="https://ws.apache.org/wss4j/config.html">WSS4J configuration: SIG_SUBJECT_CERT_CONSTRAINTS</a>
514+
*/
515+
public void setValidationSubjectDnConstraints(List<Pattern> patterns) {
516+
signatureSubjectDnPatterns = patterns;
517+
}
518+
488519
/** Whether to enable signatureConfirmation or not. By default signatureConfirmation is enabled */
489520
public void setEnableSignatureConfirmation(boolean enableSignatureConfirmation) {
490521

@@ -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

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

+34
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
}

Diff for: spring-ws-security/src/test/resources/private.jks

5.28 KB
Binary file not shown.

0 commit comments

Comments
 (0)