View Javadoc
1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements. See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied. See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.wss4j.stax.impl.processor.input;
20  
21  import java.io.BufferedInputStream;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.OutputStream;
25  import java.time.Instant;
26  import java.time.temporal.ChronoField;
27  import java.util.Arrays;
28  import java.util.HashMap;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Map;
32  
33  import javax.security.auth.callback.Callback;
34  import javax.security.auth.callback.CallbackHandler;
35  import javax.xml.namespace.QName;
36  import javax.xml.stream.XMLStreamException;
37  
38  import org.apache.wss4j.binding.wss10.TransformationParametersType;
39  import org.apache.wss4j.common.bsp.BSPRule;
40  import org.apache.wss4j.common.cache.ReplayCache;
41  import org.apache.wss4j.common.ext.Attachment;
42  import org.apache.wss4j.common.ext.AttachmentRequestCallback;
43  import org.apache.wss4j.common.ext.AttachmentResultCallback;
44  import org.apache.wss4j.common.ext.WSSecurityException;
45  import org.apache.wss4j.common.util.AttachmentUtils;
46  import org.apache.wss4j.stax.ext.WSInboundSecurityContext;
47  import org.apache.wss4j.stax.ext.WSSConstants;
48  import org.apache.wss4j.stax.ext.WSSSecurityProperties;
49  import org.apache.wss4j.stax.impl.transformer.AttachmentContentSignatureTransform;
50  import org.apache.wss4j.stax.securityEvent.SignedPartSecurityEvent;
51  import org.apache.wss4j.stax.securityEvent.TimestampSecurityEvent;
52  import org.apache.wss4j.stax.securityToken.SecurityTokenReference;
53  import org.apache.wss4j.stax.utils.WSSUtils;
54  import org.apache.xml.security.binding.excc14n.InclusiveNamespaces;
55  import org.apache.xml.security.binding.xmldsig.CanonicalizationMethodType;
56  import org.apache.xml.security.binding.xmldsig.ReferenceType;
57  import org.apache.xml.security.binding.xmldsig.SignatureType;
58  import org.apache.xml.security.binding.xmldsig.TransformType;
59  import org.apache.xml.security.exceptions.XMLSecurityException;
60  import org.apache.xml.security.stax.ext.DocumentContext;
61  import org.apache.xml.security.stax.ext.InputProcessorChain;
62  import org.apache.xml.security.stax.ext.Transformer;
63  import org.apache.xml.security.stax.ext.XMLSecurityConstants;
64  import org.apache.xml.security.stax.ext.XMLSecurityProperties;
65  import org.apache.xml.security.stax.ext.XMLSecurityUtils;
66  import org.apache.xml.security.stax.ext.stax.XMLSecEvent;
67  import org.apache.xml.security.stax.ext.stax.XMLSecStartElement;
68  import org.apache.xml.security.stax.impl.processor.input.AbstractSignatureReferenceVerifyInputProcessor;
69  import org.apache.xml.security.stax.impl.transformer.canonicalizer.Canonicalizer20010315_Excl;
70  import org.apache.xml.security.stax.impl.util.DigestOutputStream;
71  import org.apache.xml.security.stax.securityEvent.AlgorithmSuiteSecurityEvent;
72  import org.apache.xml.security.stax.securityEvent.SignedElementSecurityEvent;
73  import org.apache.xml.security.stax.securityToken.InboundSecurityToken;
74  import org.apache.xml.security.stax.securityToken.SecurityToken;
75  import org.apache.xml.security.stax.securityToken.SecurityTokenProvider;
76  import org.apache.xml.security.utils.UnsyncBufferedOutputStream;
77  
78  public class WSSSignatureReferenceVerifyInputProcessor extends AbstractSignatureReferenceVerifyInputProcessor {
79  
80      private boolean replayChecked = false;
81  
82      public WSSSignatureReferenceVerifyInputProcessor(InputProcessorChain inputProcessorChain,
83              SignatureType signatureType, InboundSecurityToken inboundSecurityToken,
84              XMLSecurityProperties securityProperties) throws XMLSecurityException {
85          super(inputProcessorChain, signatureType, inboundSecurityToken, securityProperties);
86          this.addAfterProcessor(WSSSignatureReferenceVerifyInputProcessor.class.getName());
87  
88          checkBSPCompliance((WSInboundSecurityContext)inputProcessorChain.getSecurityContext());
89      }
90  
91      @Override
92      protected void verifyExternalReference(
93              InputProcessorChain inputProcessorChain, InputStream inputStream,
94              final ReferenceType referenceType) throws XMLSecurityException, XMLStreamException {
95  
96          if (referenceType.getURI().startsWith("cid:")) {
97  
98              CallbackHandler attachmentCallbackHandler =
99                      ((WSSSecurityProperties) getSecurityProperties()).getAttachmentCallbackHandler();
100             if (attachmentCallbackHandler == null) {
101                 throw new WSSecurityException(
102                         WSSecurityException.ErrorCode.INVALID_SECURITY,
103                         "empty", new Object[] {"no attachment callbackhandler supplied"}
104                 );
105             }
106 
107             String attachmentId = AttachmentUtils.getAttachmentId(referenceType.getURI());
108 
109             AttachmentRequestCallback attachmentRequestCallback = new AttachmentRequestCallback();
110             attachmentRequestCallback.setAttachmentId(attachmentId);
111             try {
112                 attachmentCallbackHandler.handle(new Callback[]{attachmentRequestCallback});
113             } catch (Exception e) {
114                 throw new WSSecurityException(
115                         WSSecurityException.ErrorCode.INVALID_SECURITY, e);
116             }
117             List<Attachment> attachments = attachmentRequestCallback.getAttachments();
118             if (attachments == null || attachments.isEmpty() || !attachmentId.equals(attachments.get(0).getId())) {
119                 throw new WSSecurityException(
120                         WSSecurityException.ErrorCode.INVALID_SECURITY,
121                         "empty", new Object[] {"Attachment not found"}
122                 );
123             }
124 
125             final Attachment attachment = attachments.get(0);
126 
127             InputStream attachmentInputStream = attachment.getSourceStream();   //NOPMD
128             if (!attachmentInputStream.markSupported()) {
129                 attachmentInputStream = new BufferedInputStream(attachmentInputStream);
130             }
131             //todo workaround 2GB limit somehow?
132             attachmentInputStream.mark(Integer.MAX_VALUE); //we can process at maximum 2G with the standard jdk streams
133 
134             try {
135                 DigestOutputStream digestOutputStream =
136                         createMessageDigestOutputStream(referenceType, inputProcessorChain.getSecurityContext());
137                 try (UnsyncBufferedOutputStream bufferedDigestOutputStream =
138                         new UnsyncBufferedOutputStream(digestOutputStream)) {
139                     if (referenceType.getTransforms() != null) {
140                         Transformer transformer =
141                                 buildTransformerChain(referenceType, bufferedDigestOutputStream, inputProcessorChain, null);
142                         if (!(transformer instanceof AttachmentContentSignatureTransform)) {
143                             throw new WSSecurityException(
144                                     WSSecurityException.ErrorCode.INVALID_SECURITY,
145                                     "empty",
146                                     new Object[]{"First transform must be Attachment[Content|Complete]SignatureTransform"}
147                             );
148                         }
149                         Map<String, Object> transformerProperties = new HashMap<>(2);
150                         transformerProperties.put(
151                                 AttachmentContentSignatureTransform.ATTACHMENT, attachment);
152                         transformer.setProperties(transformerProperties);
153 
154                         transformer.transform(attachmentInputStream);
155                     } else {
156                         XMLSecurityUtils.copy(attachmentInputStream, bufferedDigestOutputStream);
157                     }
158                 }
159                 compareDigest(digestOutputStream.getDigestValue(), referenceType);
160 
161                 //reset the inputStream to be able to reuse it
162                 attachmentInputStream.reset();
163 
164             } catch (IOException e) {
165                 throw new XMLSecurityException(e);
166             }
167 
168             //create a new attachment and do the result callback
169             final Attachment resultAttachment = new Attachment();
170             resultAttachment.setId(attachmentId);
171             resultAttachment.setMimeType(attachment.getMimeType());
172             resultAttachment.addHeaders(attachment.getHeaders());
173             resultAttachment.setSourceStream(attachmentInputStream);
174 
175             AttachmentResultCallback attachmentResultCallback = new AttachmentResultCallback();
176             attachmentResultCallback.setAttachmentId(attachmentId);
177             attachmentResultCallback.setAttachment(resultAttachment);
178             try {
179                 attachmentCallbackHandler.handle(new Callback[]{attachmentResultCallback});
180             } catch (Exception e) {
181                 throw new WSSecurityException(
182                         WSSecurityException.ErrorCode.INVALID_SECURITY, e);
183             }
184 
185             // Create a security event for this signed Attachment
186             final DocumentContext documentContext = inputProcessorChain.getDocumentContext();
187             SignedPartSecurityEvent signedPartSecurityEvent =
188                 new SignedPartSecurityEvent(getInboundSecurityToken(), true, documentContext.getProtectionOrder());
189             signedPartSecurityEvent.setAttachment(true);
190             signedPartSecurityEvent.setCorrelationID(referenceType.getId());
191             inputProcessorChain.getSecurityContext().registerSecurityEvent(signedPartSecurityEvent);
192         } else {
193             super.verifyExternalReference(
194                     inputProcessorChain, inputStream, referenceType);
195         }
196     }
197 
198     private void checkBSPCompliance(WSInboundSecurityContext securityContext) throws WSSecurityException {
199         List<ReferenceType> references = getSignatureType().getSignedInfo().getReference();
200         for (int i = 0; i < references.size(); i++) {
201             ReferenceType referenceType = references.get(i);
202             if (referenceType.getTransforms() == null) {
203                 securityContext.handleBSPRule(BSPRule.R5416);
204             } else if (referenceType.getTransforms().getTransform().isEmpty()) {
205                 securityContext.handleBSPRule(BSPRule.R5411);
206             } else {
207                 List<TransformType> transformTypes = referenceType.getTransforms().getTransform();
208                 for (int j = 0; j < transformTypes.size(); j++) {
209                     TransformType transformType = transformTypes.get(j);
210                     final String algorithm = transformType.getAlgorithm();
211                     if (!WSSConstants.NS_C14N_EXCL.equals(algorithm)
212                             && !WSSConstants.NS_XMLDSIG_FILTER2.equals(algorithm)
213                             && !WSSConstants.SOAPMESSAGE_NS10_STR_TRANSFORM.equals(algorithm)
214                             && !WSSConstants.NS_XMLDSIG_ENVELOPED_SIGNATURE.equals(algorithm)
215                             && !WSSConstants.SWA_ATTACHMENT_CONTENT_SIG_TRANS.equals(algorithm)
216                             && !WSSConstants.SWA_ATTACHMENT_COMPLETE_SIG_TRANS.equals(algorithm)) {
217                         securityContext.handleBSPRule(BSPRule.R5423);
218                         if (j == transformTypes.size() - 1
219                             && !WSSConstants.NS_C14N_EXCL.equals(algorithm)
220                             && !WSSConstants.SOAPMESSAGE_NS10_STR_TRANSFORM.equals(algorithm)
221                             && !WSSConstants.SWA_ATTACHMENT_CONTENT_SIG_TRANS.equals(algorithm)
222                             && !WSSConstants.SWA_ATTACHMENT_COMPLETE_SIG_TRANS.equals(algorithm)) {
223                             securityContext.handleBSPRule(BSPRule.R5412);
224                         }
225                         InclusiveNamespaces inclusiveNamespacesType =
226                             XMLSecurityUtils.getQNameType(transformType.getContent(),
227                                                           XMLSecurityConstants.TAG_c14nExcl_InclusiveNamespaces);
228                         if (WSSConstants.NS_C14N_EXCL.equals(algorithm)
229                                 && inclusiveNamespacesType != null
230                                 && inclusiveNamespacesType.getPrefixList().isEmpty()) {
231                             securityContext.handleBSPRule(BSPRule.R5407);
232                         }
233                         if (WSSConstants.SOAPMESSAGE_NS10_STR_TRANSFORM.equals(algorithm)) {
234                             if (inclusiveNamespacesType != null
235                                     && inclusiveNamespacesType.getPrefixList().isEmpty()) {
236                                 securityContext.handleBSPRule(BSPRule.R5413);
237                             }
238                             TransformationParametersType transformationParametersType =
239                                     XMLSecurityUtils.getQNameType(transformType.getContent(),
240                                                                   WSSConstants.TAG_WSSE_TRANSFORMATION_PARAMETERS);
241                             if (transformationParametersType == null) {
242                                 securityContext.handleBSPRule(BSPRule.R3065);
243                             } else {
244                                 CanonicalizationMethodType canonicalizationMethodType =
245                                         XMLSecurityUtils.getQNameType(transformationParametersType.getAny(),
246                                                                       WSSConstants.TAG_dsig_CanonicalizationMethod);
247                                 if (canonicalizationMethodType == null) {
248                                     securityContext.handleBSPRule(BSPRule.R3065);
249                                 }
250                             }
251                         }
252                     }
253                 }
254             }
255             if (!(WSSConstants.NS_XMLDSIG_SHA1.equals(referenceType.getDigestMethod().getAlgorithm())
256                 || WSSConstants.NS_XENC_SHA256.equals(referenceType.getDigestMethod().getAlgorithm())
257                 || WSSConstants.NS_XENC_SHA512.equals(referenceType.getDigestMethod().getAlgorithm()))) {
258                 // Weakening this a bit to allow SHA > 1
259                 securityContext.handleBSPRule(BSPRule.R5420);
260             }
261         }
262     }
263 
264     @Override
265     public XMLSecEvent processEvent(InputProcessorChain inputProcessorChain) throws XMLStreamException, XMLSecurityException {
266 
267         //this is the earliest possible point to check for an replay attack
268         if (!replayChecked) {
269             replayChecked = true;
270             detectReplayAttack(inputProcessorChain);
271         }
272         return super.processEvent(inputProcessorChain);
273     }
274 
275     @Override
276     protected void processElementPath(List<QName> elementPath, InputProcessorChain inputProcessorChain,
277                                       XMLSecEvent xmlSecEvent, ReferenceType referenceType)
278             throws XMLSecurityException {
279         //fire a SecurityEvent:
280         final DocumentContext documentContext = inputProcessorChain.getDocumentContext();
281         if (elementPath.size() == 3 && WSSUtils.isInSOAPHeader(elementPath)
282                 || elementPath.size() == 2 && WSSUtils.isInSOAPBody(elementPath)) {
283             SignedPartSecurityEvent signedPartSecurityEvent =
284                     new SignedPartSecurityEvent(getInboundSecurityToken(), true, documentContext.getProtectionOrder());
285             signedPartSecurityEvent.setElementPath(elementPath);
286             signedPartSecurityEvent.setXmlSecEvent(xmlSecEvent);
287             signedPartSecurityEvent.setCorrelationID(referenceType.getId());
288             inputProcessorChain.getSecurityContext().registerSecurityEvent(signedPartSecurityEvent);
289         } else {
290             SignedElementSecurityEvent signedElementSecurityEvent =
291                     new SignedElementSecurityEvent(getInboundSecurityToken(), true, documentContext.getProtectionOrder());
292             signedElementSecurityEvent.setElementPath(elementPath);
293             signedElementSecurityEvent.setXmlSecEvent(xmlSecEvent);
294             signedElementSecurityEvent.setCorrelationID(referenceType.getId());
295             inputProcessorChain.getSecurityContext().registerSecurityEvent(signedElementSecurityEvent);
296         }
297     }
298 
299     @Override
300     protected InternalSignatureReferenceVerifier getSignatureReferenceVerifier(
301             XMLSecurityProperties securityProperties, InputProcessorChain inputProcessorChain,
302             ReferenceType referenceType, XMLSecStartElement startElement) throws XMLSecurityException {
303         return new WSS4JInternalSignatureReferenceVerifier((WSSSecurityProperties) securityProperties,
304                 inputProcessorChain, referenceType, startElement);
305     }
306 
307     private void detectReplayAttack(InputProcessorChain inputProcessorChain) throws WSSecurityException {
308         TimestampSecurityEvent timestampSecurityEvent =
309                 inputProcessorChain.getSecurityContext().get(WSSConstants.PROP_TIMESTAMP_SECURITYEVENT);
310         ReplayCache replayCache =   //NOPMD
311             ((WSSSecurityProperties)getSecurityProperties()).getTimestampReplayCache();
312         if (timestampSecurityEvent != null && replayCache != null) {
313             final String cacheKey =
314                     timestampSecurityEvent.getCreated().get(ChronoField.MILLI_OF_SECOND)
315                     + "" + Arrays.hashCode(getSignatureType().getSignatureValue().getValue());
316             if (replayCache.contains(cacheKey)) {
317                 throw new WSSecurityException(WSSecurityException.ErrorCode.MESSAGE_EXPIRED);
318             }
319 
320             // Store the Timestamp/SignatureValue combination in the cache
321             Instant expires = timestampSecurityEvent.getExpires();
322             if (expires != null) {
323                 replayCache.add(cacheKey, expires);
324             } else {
325                 replayCache.add(cacheKey);
326             }
327         }
328     }
329 
330     @Override
331     protected Transformer buildTransformerChain(
332             ReferenceType referenceType, OutputStream outputStream,
333             InputProcessorChain inputProcessorChain,
334             AbstractSignatureReferenceVerifyInputProcessor.InternalSignatureReferenceVerifier internalSignatureReferenceVerifier)
335             throws XMLSecurityException {
336 
337         if (referenceType.getTransforms() == null || referenceType.getTransforms().getTransform().isEmpty()) {
338             throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY);
339         }
340         List<TransformType> transformTypeList = referenceType.getTransforms().getTransform();
341 
342         if (transformTypeList.size() > maximumAllowedTransformsPerReference) {
343             throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY,
344                     "secureProcessing.MaximumAllowedTransformsPerReference",
345                     new Object[] {transformTypeList.size(), maximumAllowedTransformsPerReference});
346         }
347 
348         String algorithm = null;
349         Transformer parentTransformer = null;
350         for (int i = transformTypeList.size() - 1; i >= 0; i--) {
351             TransformType transformType = transformTypeList.get(i);
352             TransformationParametersType transformationParametersType =
353                     XMLSecurityUtils.getQNameType(transformType.getContent(), WSSConstants.TAG_WSSE_TRANSFORMATION_PARAMETERS);
354             if (transformationParametersType != null) {
355                 CanonicalizationMethodType canonicalizationMethodType =
356                         XMLSecurityUtils.getQNameType(transformationParametersType.getAny(),
357                                                       WSSConstants.TAG_dsig_CanonicalizationMethod);
358                 if (canonicalizationMethodType != null) {
359 
360                     algorithm = canonicalizationMethodType.getAlgorithm();
361 
362                     InclusiveNamespaces inclusiveNamespacesType =
363                             XMLSecurityUtils.getQNameType(canonicalizationMethodType.getContent(),
364                                                           XMLSecurityConstants.TAG_c14nExcl_InclusiveNamespaces);
365 
366                     Map<String, Object> transformerProperties = null;
367                     if (inclusiveNamespacesType != null) {
368                         transformerProperties = new HashMap<>();
369                         transformerProperties.put(
370                                 Canonicalizer20010315_Excl.INCLUSIVE_NAMESPACES_PREFIX_LIST,
371                                 inclusiveNamespacesType.getPrefixList());
372                     }
373                     parentTransformer = WSSUtils.getTransformer(
374                             null, outputStream, transformerProperties, algorithm, XMLSecurityConstants.DIRECTION.IN);
375                 }
376             }
377             algorithm = transformType.getAlgorithm();
378             AlgorithmSuiteSecurityEvent algorithmSuiteSecurityEvent = new AlgorithmSuiteSecurityEvent();
379             algorithmSuiteSecurityEvent.setAlgorithmURI(algorithm);
380             algorithmSuiteSecurityEvent.setAlgorithmUsage(WSSConstants.SigTransform);
381             algorithmSuiteSecurityEvent.setCorrelationID(referenceType.getId());
382             inputProcessorChain.getSecurityContext().registerSecurityEvent(algorithmSuiteSecurityEvent);
383 
384             InclusiveNamespaces inclusiveNamespacesType =
385                     XMLSecurityUtils.getQNameType(transformType.getContent(),
386                                                   XMLSecurityConstants.TAG_c14nExcl_InclusiveNamespaces);
387 
388             Map<String, Object> transformerProperties = null;
389             if (inclusiveNamespacesType != null) {
390                 transformerProperties = new HashMap<>();
391                 transformerProperties.put(
392                         Canonicalizer20010315_Excl.INCLUSIVE_NAMESPACES_PREFIX_LIST,
393                         inclusiveNamespacesType.getPrefixList());
394             }
395 
396             if (parentTransformer != null) {
397                 parentTransformer = WSSUtils.getTransformer(
398                         parentTransformer, null, transformerProperties, algorithm, XMLSecurityConstants.DIRECTION.IN);
399             } else {
400                 parentTransformer = WSSUtils.getTransformer(
401                         null, outputStream, transformerProperties, algorithm, XMLSecurityConstants.DIRECTION.IN);
402             }
403         }
404 
405         if (WSSConstants.SOAPMESSAGE_NS10_STR_TRANSFORM.equals(algorithm)) {
406 
407             internalSignatureReferenceVerifier.setTransformer(parentTransformer);
408 
409             String uri = XMLSecurityUtils.dropReferenceMarker(referenceType.getURI());
410             SecurityTokenProvider<? extends InboundSecurityToken> securityTokenProvider =
411                     inputProcessorChain.getSecurityContext().getSecurityTokenProvider(uri);
412             if (securityTokenProvider == null) {
413                 throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY_TOKEN, "noReference");
414             }
415             SecurityToken securityToken = securityTokenProvider.getSecurityToken();
416             if (!(securityToken instanceof SecurityTokenReference)) {
417                 throw new WSSecurityException(WSSecurityException.ErrorCode.UNSUPPORTED_SECURITY_TOKEN);
418             }
419             SecurityTokenReference securityTokenReference = (SecurityTokenReference) securityToken;
420             //todo analyse and fix me: the following statement could be problematic
421             int index = inputProcessorChain.getProcessors().indexOf(internalSignatureReferenceVerifier);
422             inputProcessorChain.getDocumentContext().setIsInSignedContent(index, internalSignatureReferenceVerifier);
423             XMLSecStartElement xmlSecStartElement = securityTokenReference.getXmlSecEvents().getLast().asStartElement();
424             internalSignatureReferenceVerifier.setStartElement(xmlSecStartElement);
425             Iterator<XMLSecEvent> xmlSecEventIterator = securityTokenReference.getXmlSecEvents().descendingIterator();
426             try {
427                 while (xmlSecEventIterator.hasNext()) {
428                     internalSignatureReferenceVerifier.processEvent(xmlSecEventIterator.next(), inputProcessorChain);
429                 }
430             } catch (XMLStreamException e) {
431                 throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY, e);
432             }
433         }
434         return parentTransformer;
435     }
436 
437     class WSS4JInternalSignatureReferenceVerifier
438             extends AbstractSignatureReferenceVerifyInputProcessor.InternalSignatureReferenceVerifier {
439 
440         WSS4JInternalSignatureReferenceVerifier(WSSSecurityProperties securityProperties, InputProcessorChain inputProcessorChain,
441                                            ReferenceType referenceType, XMLSecStartElement startElement) throws XMLSecurityException {
442             super(securityProperties, inputProcessorChain, referenceType, startElement);
443             this.addAfterProcessor(WSSSignatureReferenceVerifyInputProcessor.class.getName());
444         }
445     }
446 }