| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.wss4j.stax.impl.processor.input; |
| |
| import java.io.BufferedInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.time.Instant; |
| import java.time.temporal.ChronoField; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import javax.security.auth.callback.Callback; |
| import javax.security.auth.callback.CallbackHandler; |
| import javax.xml.namespace.QName; |
| import javax.xml.stream.XMLStreamException; |
| |
| import org.apache.wss4j.binding.wss10.TransformationParametersType; |
| import org.apache.wss4j.common.bsp.BSPRule; |
| import org.apache.wss4j.common.cache.ReplayCache; |
| import org.apache.wss4j.common.ext.Attachment; |
| import org.apache.wss4j.common.ext.AttachmentRequestCallback; |
| import org.apache.wss4j.common.ext.AttachmentResultCallback; |
| import org.apache.wss4j.common.ext.WSSecurityException; |
| import org.apache.wss4j.common.util.AttachmentUtils; |
| import org.apache.wss4j.stax.ext.WSInboundSecurityContext; |
| import org.apache.wss4j.stax.ext.WSSConstants; |
| import org.apache.wss4j.stax.ext.WSSSecurityProperties; |
| import org.apache.wss4j.stax.impl.transformer.AttachmentContentSignatureTransform; |
| import org.apache.wss4j.stax.securityEvent.SignedPartSecurityEvent; |
| import org.apache.wss4j.stax.securityEvent.TimestampSecurityEvent; |
| import org.apache.wss4j.stax.securityToken.SecurityTokenReference; |
| import org.apache.wss4j.stax.utils.WSSUtils; |
| import org.apache.xml.security.binding.excc14n.InclusiveNamespaces; |
| import org.apache.xml.security.binding.xmldsig.CanonicalizationMethodType; |
| import org.apache.xml.security.binding.xmldsig.ReferenceType; |
| import org.apache.xml.security.binding.xmldsig.SignatureType; |
| import org.apache.xml.security.binding.xmldsig.TransformType; |
| import org.apache.xml.security.exceptions.XMLSecurityException; |
| import org.apache.xml.security.stax.ext.DocumentContext; |
| import org.apache.xml.security.stax.ext.InputProcessorChain; |
| import org.apache.xml.security.stax.ext.Transformer; |
| import org.apache.xml.security.stax.ext.XMLSecurityConstants; |
| import org.apache.xml.security.stax.ext.XMLSecurityProperties; |
| import org.apache.xml.security.stax.ext.XMLSecurityUtils; |
| import org.apache.xml.security.stax.ext.stax.XMLSecEvent; |
| import org.apache.xml.security.stax.ext.stax.XMLSecStartElement; |
| import org.apache.xml.security.stax.impl.processor.input.AbstractSignatureReferenceVerifyInputProcessor; |
| import org.apache.xml.security.stax.impl.transformer.canonicalizer.Canonicalizer20010315_Excl; |
| import org.apache.xml.security.stax.impl.util.DigestOutputStream; |
| import org.apache.xml.security.stax.securityEvent.AlgorithmSuiteSecurityEvent; |
| import org.apache.xml.security.stax.securityEvent.SignedElementSecurityEvent; |
| import org.apache.xml.security.stax.securityToken.InboundSecurityToken; |
| import org.apache.xml.security.stax.securityToken.SecurityToken; |
| import org.apache.xml.security.stax.securityToken.SecurityTokenProvider; |
| import org.apache.xml.security.utils.UnsyncBufferedOutputStream; |
| |
| public class WSSSignatureReferenceVerifyInputProcessor extends AbstractSignatureReferenceVerifyInputProcessor { |
| |
| private boolean replayChecked = false; |
| |
| public WSSSignatureReferenceVerifyInputProcessor(InputProcessorChain inputProcessorChain, |
| SignatureType signatureType, InboundSecurityToken inboundSecurityToken, |
| XMLSecurityProperties securityProperties) throws XMLSecurityException { |
| super(inputProcessorChain, signatureType, inboundSecurityToken, securityProperties); |
| this.addAfterProcessor(WSSSignatureReferenceVerifyInputProcessor.class.getName()); |
| |
| checkBSPCompliance((WSInboundSecurityContext)inputProcessorChain.getSecurityContext()); |
| } |
| |
| @Override |
| protected void verifyExternalReference( |
| InputProcessorChain inputProcessorChain, InputStream inputStream, |
| final ReferenceType referenceType) throws XMLSecurityException, XMLStreamException { |
| |
| if (referenceType.getURI().startsWith("cid:")) { |
| |
| CallbackHandler attachmentCallbackHandler = |
| ((WSSSecurityProperties) getSecurityProperties()).getAttachmentCallbackHandler(); |
| if (attachmentCallbackHandler == null) { |
| throw new WSSecurityException( |
| WSSecurityException.ErrorCode.INVALID_SECURITY, |
| "empty", new Object[] {"no attachment callbackhandler supplied"} |
| ); |
| } |
| |
| String attachmentId = AttachmentUtils.getAttachmentId(referenceType.getURI()); |
| |
| AttachmentRequestCallback attachmentRequestCallback = new AttachmentRequestCallback(); |
| attachmentRequestCallback.setAttachmentId(attachmentId); |
| try { |
| attachmentCallbackHandler.handle(new Callback[]{attachmentRequestCallback}); |
| } catch (Exception e) { |
| throw new WSSecurityException( |
| WSSecurityException.ErrorCode.INVALID_SECURITY, e); |
| } |
| List<Attachment> attachments = attachmentRequestCallback.getAttachments(); |
| if (attachments == null || attachments.isEmpty() || !attachmentId.equals(attachments.get(0).getId())) { |
| throw new WSSecurityException( |
| WSSecurityException.ErrorCode.INVALID_SECURITY, |
| "empty", new Object[] {"Attachment not found"} |
| ); |
| } |
| |
| final Attachment attachment = attachments.get(0); |
| |
| InputStream attachmentInputStream = attachment.getSourceStream(); |
| if (!attachmentInputStream.markSupported()) { |
| attachmentInputStream = new BufferedInputStream(attachmentInputStream); |
| } |
| //todo workaround 2GB limit somehow? |
| attachmentInputStream.mark(Integer.MAX_VALUE); //we can process at maximum 2G with the standard jdk streams |
| |
| try { |
| DigestOutputStream digestOutputStream = |
| createMessageDigestOutputStream(referenceType, inputProcessorChain.getSecurityContext()); |
| UnsyncBufferedOutputStream bufferedDigestOutputStream = |
| new UnsyncBufferedOutputStream(digestOutputStream); |
| |
| if (referenceType.getTransforms() != null) { |
| Transformer transformer = |
| buildTransformerChain(referenceType, bufferedDigestOutputStream, inputProcessorChain, null); |
| if (!(transformer instanceof AttachmentContentSignatureTransform)) { |
| throw new WSSecurityException( |
| WSSecurityException.ErrorCode.INVALID_SECURITY, |
| "empty", |
| new Object[] {"First transform must be Attachment[Content|Complete]SignatureTransform"} |
| ); |
| } |
| Map<String, Object> transformerProperties = new HashMap<>(2); |
| transformerProperties.put( |
| AttachmentContentSignatureTransform.ATTACHMENT, attachment); |
| transformer.setProperties(transformerProperties); |
| |
| transformer.transform(attachmentInputStream); |
| |
| bufferedDigestOutputStream.close(); |
| } else { |
| XMLSecurityUtils.copy(attachmentInputStream, bufferedDigestOutputStream); |
| bufferedDigestOutputStream.close(); |
| } |
| compareDigest(digestOutputStream.getDigestValue(), referenceType); |
| |
| //reset the inputStream to be able to reuse it |
| attachmentInputStream.reset(); |
| |
| } catch (IOException e) { |
| throw new XMLSecurityException(e); |
| } |
| |
| //create a new attachment and do the result callback |
| final Attachment resultAttachment = new Attachment(); |
| resultAttachment.setId(attachmentId); |
| resultAttachment.setMimeType(attachment.getMimeType()); |
| resultAttachment.addHeaders(attachment.getHeaders()); |
| resultAttachment.setSourceStream(attachmentInputStream); |
| |
| AttachmentResultCallback attachmentResultCallback = new AttachmentResultCallback(); |
| attachmentResultCallback.setAttachmentId(attachmentId); |
| attachmentResultCallback.setAttachment(resultAttachment); |
| try { |
| attachmentCallbackHandler.handle(new Callback[]{attachmentResultCallback}); |
| } catch (Exception e) { |
| throw new WSSecurityException( |
| WSSecurityException.ErrorCode.INVALID_SECURITY, e); |
| } |
| |
| // Create a security event for this signed Attachment |
| final DocumentContext documentContext = inputProcessorChain.getDocumentContext(); |
| SignedPartSecurityEvent signedPartSecurityEvent = |
| new SignedPartSecurityEvent(getInboundSecurityToken(), true, documentContext.getProtectionOrder()); |
| signedPartSecurityEvent.setAttachment(true); |
| signedPartSecurityEvent.setCorrelationID(referenceType.getId()); |
| inputProcessorChain.getSecurityContext().registerSecurityEvent(signedPartSecurityEvent); |
| } else { |
| super.verifyExternalReference( |
| inputProcessorChain, inputStream, referenceType); |
| } |
| } |
| |
| private void checkBSPCompliance(WSInboundSecurityContext securityContext) throws WSSecurityException { |
| List<ReferenceType> references = getSignatureType().getSignedInfo().getReference(); |
| for (int i = 0; i < references.size(); i++) { |
| ReferenceType referenceType = references.get(i); |
| if (referenceType.getTransforms() == null) { |
| securityContext.handleBSPRule(BSPRule.R5416); |
| } else if (referenceType.getTransforms().getTransform().isEmpty()) { |
| securityContext.handleBSPRule(BSPRule.R5411); |
| } else { |
| List<TransformType> transformTypes = referenceType.getTransforms().getTransform(); |
| for (int j = 0; j < transformTypes.size(); j++) { |
| TransformType transformType = transformTypes.get(j); |
| final String algorithm = transformType.getAlgorithm(); |
| if (!WSSConstants.NS_C14N_EXCL.equals(algorithm) |
| && !WSSConstants.NS_XMLDSIG_FILTER2.equals(algorithm) |
| && !WSSConstants.SOAPMESSAGE_NS10_STR_TRANSFORM.equals(algorithm) |
| && !WSSConstants.NS_XMLDSIG_ENVELOPED_SIGNATURE.equals(algorithm) |
| && !WSSConstants.SWA_ATTACHMENT_CONTENT_SIG_TRANS.equals(algorithm) |
| && !WSSConstants.SWA_ATTACHMENT_COMPLETE_SIG_TRANS.equals(algorithm)) { |
| securityContext.handleBSPRule(BSPRule.R5423); |
| if (j == transformTypes.size() - 1 |
| && !WSSConstants.NS_C14N_EXCL.equals(algorithm) |
| && !WSSConstants.SOAPMESSAGE_NS10_STR_TRANSFORM.equals(algorithm) |
| && !WSSConstants.SWA_ATTACHMENT_CONTENT_SIG_TRANS.equals(algorithm) |
| && !WSSConstants.SWA_ATTACHMENT_COMPLETE_SIG_TRANS.equals(algorithm)) { |
| securityContext.handleBSPRule(BSPRule.R5412); |
| } |
| InclusiveNamespaces inclusiveNamespacesType = |
| XMLSecurityUtils.getQNameType(transformType.getContent(), |
| XMLSecurityConstants.TAG_c14nExcl_InclusiveNamespaces); |
| if (WSSConstants.NS_C14N_EXCL.equals(algorithm) |
| && inclusiveNamespacesType != null |
| && inclusiveNamespacesType.getPrefixList().isEmpty()) { |
| securityContext.handleBSPRule(BSPRule.R5407); |
| } |
| if (WSSConstants.SOAPMESSAGE_NS10_STR_TRANSFORM.equals(algorithm)) { |
| if (inclusiveNamespacesType != null |
| && inclusiveNamespacesType.getPrefixList().isEmpty()) { |
| securityContext.handleBSPRule(BSPRule.R5413); |
| } |
| TransformationParametersType transformationParametersType = |
| XMLSecurityUtils.getQNameType(transformType.getContent(), |
| WSSConstants.TAG_WSSE_TRANSFORMATION_PARAMETERS); |
| if (transformationParametersType == null) { |
| securityContext.handleBSPRule(BSPRule.R3065); |
| } else { |
| CanonicalizationMethodType canonicalizationMethodType = |
| XMLSecurityUtils.getQNameType(transformationParametersType.getAny(), |
| WSSConstants.TAG_dsig_CanonicalizationMethod); |
| if (canonicalizationMethodType == null) { |
| securityContext.handleBSPRule(BSPRule.R3065); |
| } |
| } |
| } |
| } |
| } |
| } |
| if (!(WSSConstants.NS_XMLDSIG_SHA1.equals(referenceType.getDigestMethod().getAlgorithm()) |
| || WSSConstants.NS_XENC_SHA256.equals(referenceType.getDigestMethod().getAlgorithm()) |
| || WSSConstants.NS_XENC_SHA512.equals(referenceType.getDigestMethod().getAlgorithm()))) { |
| // Weakening this a bit to allow SHA > 1 |
| securityContext.handleBSPRule(BSPRule.R5420); |
| } |
| } |
| } |
| |
| @Override |
| public XMLSecEvent processEvent(InputProcessorChain inputProcessorChain) throws XMLStreamException, XMLSecurityException { |
| |
| //this is the earliest possible point to check for an replay attack |
| if (!replayChecked) { |
| replayChecked = true; |
| detectReplayAttack(inputProcessorChain); |
| } |
| return super.processEvent(inputProcessorChain); |
| } |
| |
| @Override |
| protected void processElementPath(List<QName> elementPath, InputProcessorChain inputProcessorChain, |
| XMLSecEvent xmlSecEvent, ReferenceType referenceType) |
| throws XMLSecurityException { |
| //fire a SecurityEvent: |
| final DocumentContext documentContext = inputProcessorChain.getDocumentContext(); |
| if (elementPath.size() == 3 && WSSUtils.isInSOAPHeader(elementPath) |
| || elementPath.size() == 2 && WSSUtils.isInSOAPBody(elementPath)) { |
| SignedPartSecurityEvent signedPartSecurityEvent = |
| new SignedPartSecurityEvent(getInboundSecurityToken(), true, documentContext.getProtectionOrder()); |
| signedPartSecurityEvent.setElementPath(elementPath); |
| signedPartSecurityEvent.setXmlSecEvent(xmlSecEvent); |
| signedPartSecurityEvent.setCorrelationID(referenceType.getId()); |
| inputProcessorChain.getSecurityContext().registerSecurityEvent(signedPartSecurityEvent); |
| } else { |
| SignedElementSecurityEvent signedElementSecurityEvent = |
| new SignedElementSecurityEvent(getInboundSecurityToken(), true, documentContext.getProtectionOrder()); |
| signedElementSecurityEvent.setElementPath(elementPath); |
| signedElementSecurityEvent.setXmlSecEvent(xmlSecEvent); |
| signedElementSecurityEvent.setCorrelationID(referenceType.getId()); |
| inputProcessorChain.getSecurityContext().registerSecurityEvent(signedElementSecurityEvent); |
| } |
| } |
| |
| @Override |
| protected InternalSignatureReferenceVerifier getSignatureReferenceVerifier( |
| XMLSecurityProperties securityProperties, InputProcessorChain inputProcessorChain, |
| ReferenceType referenceType, XMLSecStartElement startElement) throws XMLSecurityException { |
| return new InternalSignatureReferenceVerifier((WSSSecurityProperties) securityProperties, |
| inputProcessorChain, referenceType, startElement); |
| } |
| |
| private void detectReplayAttack(InputProcessorChain inputProcessorChain) throws WSSecurityException { |
| TimestampSecurityEvent timestampSecurityEvent = |
| inputProcessorChain.getSecurityContext().get(WSSConstants.PROP_TIMESTAMP_SECURITYEVENT); |
| ReplayCache replayCache = |
| ((WSSSecurityProperties)getSecurityProperties()).getTimestampReplayCache(); |
| if (timestampSecurityEvent != null && replayCache != null) { |
| final String cacheKey = |
| timestampSecurityEvent.getCreated().get(ChronoField.MILLI_OF_SECOND) |
| + "" + Arrays.hashCode(getSignatureType().getSignatureValue().getValue()); |
| if (replayCache.contains(cacheKey)) { |
| throw new WSSecurityException(WSSecurityException.ErrorCode.MESSAGE_EXPIRED); |
| } |
| |
| // Store the Timestamp/SignatureValue combination in the cache |
| Instant expires = timestampSecurityEvent.getExpires(); |
| if (expires != null) { |
| replayCache.add(cacheKey, expires); |
| } else { |
| replayCache.add(cacheKey); |
| } |
| } |
| } |
| |
| @Override |
| protected Transformer buildTransformerChain( |
| ReferenceType referenceType, OutputStream outputStream, |
| InputProcessorChain inputProcessorChain, |
| AbstractSignatureReferenceVerifyInputProcessor.InternalSignatureReferenceVerifier internalSignatureReferenceVerifier) |
| throws XMLSecurityException { |
| |
| if (referenceType.getTransforms() == null || referenceType.getTransforms().getTransform().isEmpty()) { |
| throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY); |
| } |
| List<TransformType> transformTypeList = referenceType.getTransforms().getTransform(); |
| |
| if (transformTypeList.size() > maximumAllowedTransformsPerReference) { |
| throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY, |
| "secureProcessing.MaximumAllowedTransformsPerReference", |
| new Object[] {transformTypeList.size(), maximumAllowedTransformsPerReference}); |
| } |
| |
| String algorithm = null; |
| Transformer parentTransformer = null; |
| for (int i = transformTypeList.size() - 1; i >= 0; i--) { |
| TransformType transformType = transformTypeList.get(i); |
| TransformationParametersType transformationParametersType = |
| XMLSecurityUtils.getQNameType(transformType.getContent(), WSSConstants.TAG_WSSE_TRANSFORMATION_PARAMETERS); |
| if (transformationParametersType != null) { |
| CanonicalizationMethodType canonicalizationMethodType = |
| XMLSecurityUtils.getQNameType(transformationParametersType.getAny(), |
| WSSConstants.TAG_dsig_CanonicalizationMethod); |
| if (canonicalizationMethodType != null) { |
| |
| algorithm = canonicalizationMethodType.getAlgorithm(); |
| |
| InclusiveNamespaces inclusiveNamespacesType = |
| XMLSecurityUtils.getQNameType(canonicalizationMethodType.getContent(), |
| XMLSecurityConstants.TAG_c14nExcl_InclusiveNamespaces); |
| |
| Map<String, Object> transformerProperties = null; |
| if (inclusiveNamespacesType != null) { |
| transformerProperties = new HashMap<>(); |
| transformerProperties.put( |
| Canonicalizer20010315_Excl.INCLUSIVE_NAMESPACES_PREFIX_LIST, |
| inclusiveNamespacesType.getPrefixList()); |
| } |
| parentTransformer = WSSUtils.getTransformer( |
| null, outputStream, transformerProperties, algorithm, XMLSecurityConstants.DIRECTION.IN); |
| } |
| } |
| algorithm = transformType.getAlgorithm(); |
| AlgorithmSuiteSecurityEvent algorithmSuiteSecurityEvent = new AlgorithmSuiteSecurityEvent(); |
| algorithmSuiteSecurityEvent.setAlgorithmURI(algorithm); |
| algorithmSuiteSecurityEvent.setAlgorithmUsage(WSSConstants.SigTransform); |
| algorithmSuiteSecurityEvent.setCorrelationID(referenceType.getId()); |
| inputProcessorChain.getSecurityContext().registerSecurityEvent(algorithmSuiteSecurityEvent); |
| |
| InclusiveNamespaces inclusiveNamespacesType = |
| XMLSecurityUtils.getQNameType(transformType.getContent(), |
| XMLSecurityConstants.TAG_c14nExcl_InclusiveNamespaces); |
| |
| Map<String, Object> transformerProperties = null; |
| if (inclusiveNamespacesType != null) { |
| transformerProperties = new HashMap<>(); |
| transformerProperties.put( |
| Canonicalizer20010315_Excl.INCLUSIVE_NAMESPACES_PREFIX_LIST, |
| inclusiveNamespacesType.getPrefixList()); |
| } |
| |
| if (parentTransformer != null) { |
| parentTransformer = WSSUtils.getTransformer( |
| parentTransformer, null, transformerProperties, algorithm, XMLSecurityConstants.DIRECTION.IN); |
| } else { |
| parentTransformer = WSSUtils.getTransformer( |
| null, outputStream, transformerProperties, algorithm, XMLSecurityConstants.DIRECTION.IN); |
| } |
| } |
| |
| if (WSSConstants.SOAPMESSAGE_NS10_STR_TRANSFORM.equals(algorithm)) { |
| |
| internalSignatureReferenceVerifier.setTransformer(parentTransformer); |
| |
| String uri = XMLSecurityUtils.dropReferenceMarker(referenceType.getURI()); |
| SecurityTokenProvider<? extends InboundSecurityToken> securityTokenProvider = |
| inputProcessorChain.getSecurityContext().getSecurityTokenProvider(uri); |
| if (securityTokenProvider == null) { |
| throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY_TOKEN, "noReference"); |
| } |
| SecurityToken securityToken = securityTokenProvider.getSecurityToken(); |
| if (!(securityToken instanceof SecurityTokenReference)) { |
| throw new WSSecurityException(WSSecurityException.ErrorCode.UNSUPPORTED_SECURITY_TOKEN); |
| } |
| SecurityTokenReference securityTokenReference = (SecurityTokenReference) securityToken; |
| //todo analyse and fix me: the following statement could be problematic |
| int index = inputProcessorChain.getProcessors().indexOf(internalSignatureReferenceVerifier); |
| inputProcessorChain.getDocumentContext().setIsInSignedContent(index, internalSignatureReferenceVerifier); |
| XMLSecStartElement xmlSecStartElement = securityTokenReference.getXmlSecEvents().getLast().asStartElement(); |
| internalSignatureReferenceVerifier.setStartElement(xmlSecStartElement); |
| Iterator<XMLSecEvent> xmlSecEventIterator = securityTokenReference.getXmlSecEvents().descendingIterator(); |
| try { |
| while (xmlSecEventIterator.hasNext()) { |
| internalSignatureReferenceVerifier.processEvent(xmlSecEventIterator.next(), inputProcessorChain); |
| } |
| } catch (XMLStreamException e) { |
| throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY, e); |
| } |
| } |
| return parentTransformer; |
| } |
| |
| class InternalSignatureReferenceVerifier extends AbstractSignatureReferenceVerifyInputProcessor.InternalSignatureReferenceVerifier { |
| |
| InternalSignatureReferenceVerifier(WSSSecurityProperties securityProperties, InputProcessorChain inputProcessorChain, |
| ReferenceType referenceType, XMLSecStartElement startElement) throws XMLSecurityException { |
| super(securityProperties, inputProcessorChain, referenceType, startElement); |
| this.addAfterProcessor(WSSSignatureReferenceVerifyInputProcessor.class.getName()); |
| } |
| } |
| } |