no.digipost.api.interceptors.Wss4jInterceptor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sdp-api-commons Show documentation
Show all versions of sdp-api-commons Show documentation
Felles kode for API-relatert funksjonalitet
The newest version!
package no.digipost.api.interceptors;
import no.digipost.api.security.OrgnummerExtractor;
import no.digipost.api.xml.Constants;
import org.apache.wss4j.common.ConfigurationConstants;
import org.apache.wss4j.common.crypto.AlgorithmSuite;
import org.apache.wss4j.common.crypto.Crypto;
import org.apache.wss4j.common.ext.WSSecurityException;
import org.apache.wss4j.dom.WSConstants;
import org.apache.wss4j.dom.WSDataRef;
import org.apache.wss4j.dom.engine.WSSConfig;
import org.apache.wss4j.dom.engine.WSSecurityEngine;
import org.apache.wss4j.dom.engine.WSSecurityEngineResult;
import org.apache.wss4j.dom.handler.RequestData;
import org.apache.wss4j.dom.handler.WSHandlerConstants;
import org.apache.wss4j.dom.handler.WSHandlerResult;
import org.apache.wss4j.dom.message.token.Timestamp;
import org.apache.wss4j.dom.util.WSSecurityUtil;
import org.apache.wss4j.dom.validate.Credential;
import org.apache.wss4j.dom.validate.SignatureTrustValidator;
import org.apache.wss4j.dom.validate.TimestampValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.ws.context.MessageContext;
import org.springframework.ws.server.EndpointExceptionResolver;
import org.springframework.ws.soap.SoapBody;
import org.springframework.ws.soap.SoapMessage;
import org.springframework.ws.soap.security.AbstractWsSecurityInterceptor;
import org.springframework.ws.soap.security.WsSecurityFaultException;
import org.springframework.ws.soap.security.WsSecuritySecurementException;
import org.springframework.ws.soap.security.WsSecurityValidationException;
import org.springframework.ws.soap.security.callback.CleanupCallback;
import org.springframework.ws.soap.security.wss4j2.Wss4jSecuritySecurementException;
import org.springframework.ws.soap.security.wss4j2.Wss4jSecurityValidationException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.DigestMethod;
import javax.xml.namespace.QName;
import java.io.IOException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import static java.util.Collections.emptyList;
import static no.digipost.api.xml.Constants.HEADER_QNAME;
import static no.digipost.api.xml.Constants.MESSAGING_QNAME;
import static no.digipost.api.xml.Constants.WSSEC_HEADER_QNAME;
import static no.digipost.api.xml.Constants.WSU_TIMESTAMP_QNAME;
public class Wss4jInterceptor extends AbstractWsSecurityInterceptor {
public static final Logger LOG = LoggerFactory.getLogger(Wss4jInterceptor.class);
public static final String SECUREMENT_USER_PROPERTY_NAME = "Wss4jSecurityInterceptor.securementUser";
public static final String INCOMING_CERTIFICATE = "Wss4jInterceptor.incoming.certificate";
private static final Logger logger = LoggerFactory.getLogger(Wss4jInterceptor.class);
private final boolean timestampStrict = true;
private final WSSecurityEngine securityEngine = new WSSecurityEngine();
private final Wss4jHandler handler = new Wss4jHandler();
private String securementActions;
private List securementActionsVector;
private String securementUsername;
private CallbackHandler validationCallbackHandler;
private String validationActions;
private List validationActionsVector;
private Crypto validationSignatureCrypto;
private boolean enableSignatureConfirmation;
private int validationTimeToLive = 120;
private int securementTimeToLive = 120;
private WSSConfig wssConfig;
private boolean enableRevocation;
private String digestAlgorithm;
private String securementSignatureAlgorithm;
private LogFault logFault;
private EndpointExceptionResolver exceptionResolver;
public Wss4jInterceptor(EndpointExceptionResolver exceptionResolver) {
this(new LogFault.LogFaultsAsWarn(LOG), exceptionResolver);
}
public Wss4jInterceptor(LogFault logFault, EndpointExceptionResolver exceptionResolver) {
setExceptionResolver(exceptionResolver);
this.exceptionResolver = exceptionResolver;
this.logFault = logFault;
setSecurementSignatureAlgorithm(Constants.RSA_SHA256);
setSecurementSignatureDigestAlgorithm(DigestMethod.SHA256);
setSecurementSignatureKeyIdentifier("DirectReference");
setSecurementActions("Timestamp Signature");
setValidationActions("Timestamp Signature");
}
public void setSecurementActions(final String actions) {
securementActions = actions;
try {
securementActionsVector = WSSecurityUtil.decodeAction(securementActions);
} catch (WSSecurityException ex) {
throw new IllegalArgumentException(ex);
}
}
public void setSecurementActor(final String securementActor) {
handler.setOption(WSHandlerConstants.ACTOR, securementActor);
}
public void setSecurementPassword(final String securementPassword) {
handler.setSecurementPassword(securementPassword);
}
public void setSecurementSignatureAlgorithm(final String securementSignatureAlgorithm) {
handler.setOption(WSHandlerConstants.SIG_ALGO, securementSignatureAlgorithm);
this.securementSignatureAlgorithm = securementSignatureAlgorithm;
}
public void setSecurementSignatureDigestAlgorithm(final String digestAlgorithm) {
handler.setOption(WSHandlerConstants.SIG_DIGEST_ALGO, digestAlgorithm);
this.digestAlgorithm = digestAlgorithm;
}
public void setSecurementSignatureCrypto(final Crypto securementSignatureCrypto) {
handler.setSecurementSignatureCrypto(securementSignatureCrypto);
}
public void setSecurementSignatureKeyIdentifier(final String securementSignatureKeyIdentifier) {
handler.setOption(WSHandlerConstants.SIG_KEY_ID, securementSignatureKeyIdentifier);
}
public void setSecurementSignatureParts(final String securementSignatureParts) {
handler.setOption(WSHandlerConstants.SIGNATURE_PARTS, securementSignatureParts);
}
public void setSecurementSignatureIfPresentParts(final String securementSignatureParts) {
handler.setOption(ConfigurationConstants.OPTIONAL_SIGNATURE_PARTS, securementSignatureParts);
}
public void setSecurementSignatureUser(final String securementSignatureUser) {
handler.setOption(WSHandlerConstants.SIGNATURE_USER, securementSignatureUser);
}
/**
* Sets the time to live on the outgoing message
*/
public void setSecurementTimeToLive(final int ttl) {
if (ttl <= 0) {
throw new IllegalArgumentException("timeToLive must be positive");
}
securementTimeToLive = ttl;
}
public void setValidationTimeToLive(final int ttl) {
if (ttl <= 0) {
throw new IllegalArgumentException("timeToLive must be positive");
}
validationTimeToLive = ttl;
}
public void setValidationActions(final String actions) {
validationActions = actions;
try {
validationActionsVector = WSSecurityUtil.decodeAction(actions);
} catch (WSSecurityException ex) {
throw new IllegalArgumentException(ex);
}
}
public void setValidationSignatureCrypto(final Crypto signatureCrypto) {
validationSignatureCrypto = signatureCrypto;
}
public void setEnableRevocation(final boolean enabled) {
enableRevocation = enabled;
}
public void setBspCompliant(final boolean compliant) {
handler.setOption(WSHandlerConstants.IS_BSP_COMPLIANT, compliant);
}
public void afterPropertiesSet() throws Exception {
Assert.isTrue(validationActions != null || securementActions != null,
"validationActions or securementActions are required");
if (validationActions != null) {
if (validationActionsVector.contains(WSConstants.UT)) {
Assert.notNull(validationCallbackHandler, "validationCallbackHandler is required");
}
if (validationActionsVector.contains(WSConstants.SIGN)) {
Assert.notNull(validationSignatureCrypto, "validationSignatureCrypto is required");
}
}
}
@Override //Overridden in order to be able to set the level of logging
protected boolean handleValidationException(WsSecurityValidationException ex, MessageContext messageContext) {
logFault.log(ex);
if (this.exceptionResolver != null) {
this.exceptionResolver.resolveException(messageContext, (Object) null, ex);
} else {
if (logger.isDebugEnabled()) {
logger.debug("No exception resolver present, creating basic soap fault");
}
SoapBody response = ((SoapMessage) messageContext.getResponse()).getSoapBody();
response.addClientOrSenderFault(ex.getMessage(), Locale.ENGLISH);
}
return false;
}
@Override
protected boolean handleFaultException(final WsSecurityFaultException ex, final MessageContext messageContext) {
logFault.log(ex);
throw new RuntimeException("Could not handle request", ex);
}
@Override
protected boolean handleSecurementException(WsSecuritySecurementException ex, MessageContext messageContext) {
logFault.log(ex);
throw ex;
}
@Override
protected void secureMessage(final SoapMessage soapMessage, final MessageContext messageContext) throws WsSecuritySecurementException {
boolean noSecurity = securementActionsVector.isEmpty() || securementActionsVector.contains(0);
if (noSecurity && !enableSignatureConfirmation) {
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Securing message [" + soapMessage + "] with actions [" + securementActions + "]");
}
RequestData requestData = initializeRequestData(messageContext);
requestData.setAttachmentCallbackHandler(new AttachmentCallbackHandler(soapMessage));
Document envelopeAsDocument = soapMessage.getDocument();
try {
// In case on signature confirmation with no other securement
// action, we need to pass an empty securementActionsVector to avoid
// NPE
if (noSecurity) {
securementActionsVector = new ArrayList(0);
}
handler.doSenderAction(envelopeAsDocument, requestData, WSSecurityUtil.decodeHandlerAction(securementActions, wssConfig), true);
} catch (WSSecurityException ex) {
throw new Wss4jSecuritySecurementException(ex.getMessage(), ex);
}
soapMessage.setDocument(envelopeAsDocument);
}
/**
* Creates and initializes a request data for the given message context.
*
* @param messageContext the message context
* @return the request data
*/
protected RequestData initializeRequestData(final MessageContext messageContext) {
RequestData requestData = new RequestData();
requestData.setMsgContext(messageContext);
// reads securementUsername first from the context then from the property
String contextUsername = (String) messageContext.getProperty(SECUREMENT_USER_PROPERTY_NAME);
if (StringUtils.hasLength(contextUsername)) {
requestData.setUsername(contextUsername);
} else {
requestData.setUsername(securementUsername);
}
requestData.setAppendSignatureAfterTimestamp(true);
requestData.setTimeStampTTL(securementTimeToLive);
requestData.setWssConfig(wssConfig);
return requestData;
}
@Override
protected void validateMessage(final SoapMessage soapMessage, final MessageContext messageContext)
throws WsSecurityValidationException {
if (logger.isDebugEnabled()) {
logger.debug("Validating message [" + soapMessage + "] with actions [" + validationActions + "]");
}
if (validationActionsVector.isEmpty() || validationActionsVector.contains(WSConstants.NO_SECURITY)) {
return;
}
Document envelopeAsDocument = soapMessage.getDocument();
// Header processing
try {
RequestData requestData = new RequestData();
requestData.setAttachmentCallbackHandler(new AttachmentCallbackHandler(soapMessage));
requestData.setWssConfig(wssConfig);
requestData.setSigVerCrypto(validationSignatureCrypto);
requestData.setCallbackHandler(validationCallbackHandler);
requestData.setSubjectCertConstraints(OrgnummerExtractor.PATTERNS);
AlgorithmSuite algorithmSuite = new AlgorithmSuite();
algorithmSuite.addDigestAlgorithm(digestAlgorithm);
algorithmSuite.addSignatureMethod(securementSignatureAlgorithm);
algorithmSuite.addC14nAlgorithm(CanonicalizationMethod.EXCLUSIVE);
//algorithmSuite.addTransformAlgorithm("http://www.w3.org/2001/10/xml-exc-c14n#");
//algorithmSuite.addTransformAlgorithm("http://docs.oasis-open.org/wss/2004/XX/oasis-2004XX-wss-swa-profile-1.0#Attachment-Complete-Transform");
requestData.setAlgorithmSuite(algorithmSuite);
WSHandlerResult handlerResult = securityEngine.processSecurityHeader(envelopeAsDocument, requestData);
// Results verification
if (handlerResult == null) {
throw new Wss4jSecurityValidationException("No WS-Security header found");
}
updateMessageContextWithCertificate(messageContext, handlerResult);
checkResults(handlerResult.getResults(), validationActionsVector);
validateEbmsMessagingIsSigned(envelopeAsDocument, handlerResult.getResults());
validateTimestampIsSigned(envelopeAsDocument, handlerResult.getResults());
// puts the results in the context
// useful for Signature Confirmation
updateContextWithResults(messageContext, handlerResult);
verifyCertificateTrust(handlerResult);
verifyTimestamp(handlerResult);
} catch (WSSecurityException ex) {
throw new Wss4jSecurityValidationException(ex.getMessage(), ex);
}
soapMessage.setDocument(envelopeAsDocument);
soapMessage.getEnvelope().getHeader().removeHeaderElement(WS_SECURITY_NAME);
}
private void updateMessageContextWithCertificate(MessageContext messageContext, WSHandlerResult result) {
List signResults = result.getActionResults().getOrDefault(WSConstants.SIGN, emptyList());
if (signResults.isEmpty()) {
throw new Wss4jSecurityValidationException("No action results for 'Perform Signature' found");
} else if (signResults.size() > 1) {
throw new Wss4jSecurityValidationException("Multiple action results for 'Perform Signature' found. Expected only 1.");
}
WSSecurityEngineResult signResult = signResults.get(0);
X509Certificate cert = (X509Certificate) signResult.get(WSSecurityEngineResult.TAG_X509_CERTIFICATE);
if (cert != null) {
messageContext.setProperty(INCOMING_CERTIFICATE, cert);
}
}
private void validateTimestampIsSigned(final Document doc, final List results) {
validateIsSigned(doc, results, HEADER_QNAME, WSSEC_HEADER_QNAME, WSU_TIMESTAMP_QNAME);
}
private void validateEbmsMessagingIsSigned(final Document doc, final List results) {
validateIsSigned(doc, results, HEADER_QNAME, MESSAGING_QNAME);
}
private void validateIsSigned(final Document doc, final List results, final QName... qnamePath) {
if (!wasSigned(doc, results, qnamePath)) {
QName qName = qnamePath[qnamePath.length - 1];
throw new Wss4jSecurityValidationException(qName.getPrefix() + ":" + qName.getLocalPart() + " was not signed");
}
}
private boolean wasSigned(final Document doc, final List results, final QName... qnamePath) {
String path = "/" + doc.getDocumentElement().getPrefix() + ":Envelope";
for (QName qn : qnamePath) {
Node n = doc.getDocumentElement().getElementsByTagNameNS(qn.getNamespaceURI(), qn.getLocalPart()).item(0);
if (n == null) {
return false;
}
path += "/" + n.getPrefix() + ":" + n.getLocalName();
}
for (WSSecurityEngineResult r : results) {
if (r.containsKey("data-ref-uris")) {
@SuppressWarnings("unchecked")
List refs = (List) r.get("data-ref-uris");
for (WSDataRef ref : refs) {
if (ref.getName().equals(qnamePath[qnamePath.length - 1])) {
if (ref.getXpath().equals(path)) {
return true;
}
}
}
}
}
return false;
}
/**
* Checks whether the received headers match the configured validation actions. Subclasses could override this method
* for custom verification behavior.
*
* @param results the results of the validation function
* @param validationActions the decoded validation actions
* @throws Wss4jSecurityValidationException if the results are deemed invalid
*/
protected void checkResults(final List results, final List validationActions) throws Wss4jSecurityValidationException {
if (!handler.checkReceiverResultsAnyOrder(results, validationActions)) {
throw new Wss4jSecurityValidationException("Security processing failed (actions mismatch)");
}
}
/**
* Puts the results of WS-Security headers processing in the message context. Some actions like Signature
* Confirmation require this.
*/
@SuppressWarnings("unchecked")
private void updateContextWithResults(final MessageContext messageContext, final WSHandlerResult result) {
List handlerResults;
if ((handlerResults = (List) messageContext.getProperty(WSHandlerConstants.RECV_RESULTS)) == null) {
handlerResults = new ArrayList();
messageContext.setProperty(WSHandlerConstants.RECV_RESULTS, handlerResults);
}
handlerResults.add(0, result);
messageContext.setProperty(WSHandlerConstants.RECV_RESULTS, handlerResults);
}
/**
* Verifies the trust of a certificate.
*/
protected void verifyCertificateTrust(WSHandlerResult result) throws WSSecurityException {
List signResults = result.getActionResults().getOrDefault(WSConstants.SIGN, emptyList());
if (signResults.isEmpty()) {
throw new Wss4jSecurityValidationException("No action results for 'Perform Signature' found");
} else if (signResults.size() > 1) {
throw new Wss4jSecurityValidationException("Multiple action results for 'Perform Signature' found. Expected only 1.");
}
WSSecurityEngineResult signResult = signResults.get(0);
if (signResult != null) {
X509Certificate returnCert =
(X509Certificate) signResult.get(WSSecurityEngineResult.TAG_X509_CERTIFICATE);
Credential credential = new Credential();
credential.setCertificates(new X509Certificate[]{returnCert});
RequestData requestData = new RequestData();
requestData.setSigVerCrypto(validationSignatureCrypto);
requestData.setEnableRevocation(enableRevocation);
requestData.setSubjectCertConstraints(OrgnummerExtractor.PATTERNS);
SignatureTrustValidator validator = new SignatureTrustValidator();
validator.validate(credential, requestData);
}
}
/**
* Verifies the timestamp.
*/
protected void verifyTimestamp(WSHandlerResult result) throws WSSecurityException {
List insertTimestampResults = result.getActionResults().getOrDefault(WSConstants.TS, emptyList());
if (insertTimestampResults.isEmpty()) {
throw new Wss4jSecurityValidationException("No action results for 'Insert timestamp' found");
} else if (insertTimestampResults.size() > 1) {
throw new Wss4jSecurityValidationException("Multiple action results for 'Insert timestamp' found. Expected only 1.");
}
WSSecurityEngineResult actionResult = insertTimestampResults.get(0);
if (actionResult != null) {
Timestamp timestamp = (Timestamp) actionResult.get(WSSecurityEngineResult.TAG_TIMESTAMP);
if (timestamp != null && timestampStrict) {
Credential credential = new Credential();
credential.setTimestamp(timestamp);
RequestData requestData = new RequestData();
requestData.setWssConfig(WSSConfig.getNewInstance());
requestData.setTimeStampTTL(validationTimeToLive);
requestData.setTimeStampStrict(timestampStrict);
TimestampValidator validator = new TimestampValidator();
validator.validate(credential, requestData);
}
}
}
@Override
protected void cleanUp() {
if (validationCallbackHandler != null) {
try {
CleanupCallback cleanupCallback = new CleanupCallback();
validationCallbackHandler.handle(new Callback[]{cleanupCallback});
} catch (IOException ex) {
logger.warn("Cleanup callback resulted in IOException", ex);
} catch (UnsupportedCallbackException ex) {
// ignore
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy