Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable sign with cert chain and configuration of subjectDnConstraints #1419

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -138,6 +144,7 @@
* @author Jamin Hitchcock
* @author Rob Leland
* @author Lars Uffmann
* @author Andreas Winter
* @see <a href="http://ws.apache.org/wss4j/">Apache WSS4J 2.0</a>
* @since 2.3.0
*/
Expand Down Expand Up @@ -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<Pattern> signatureSubjectDnPatterns = emptyList();

/**
* Create a {@link WSSecurityEngine} by default.
*/
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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 <a href="https://ws.apache.org/wss4j/config.html">WSS4J configuration: SIG_SUBJECT_CERT_CONSTRAINTS</a>
*/
public void setValidationSubjectDnConstraints(List<Pattern> patterns) {
signatureSubjectDnPatterns = patterns;
}

/** Whether to enable signatureConfirmation or not. By default signatureConfirmation is enabled */
public void setEnableSignatureConfirmation(boolean enableSignatureConfirmation) {

Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
}
}
Binary file modified spring-ws-security/src/test/resources/private.jks
Binary file not shown.