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  
20  package org.apache.wss4j.dom.message;
21  
22  import java.security.InvalidAlgorithmParameterException;
23  import java.security.NoSuchAlgorithmException;
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.List;
27  
28  import javax.security.auth.callback.Callback;
29  import javax.xml.crypto.XMLStructure;
30  import javax.xml.crypto.dom.DOMStructure;
31  import javax.xml.crypto.dsig.DigestMethod;
32  import javax.xml.crypto.dsig.Transform;
33  import javax.xml.crypto.dsig.XMLSignatureFactory;
34  import javax.xml.crypto.dsig.spec.ExcC14NParameterSpec;
35  import javax.xml.crypto.dsig.spec.TransformParameterSpec;
36  
37  
38  import org.apache.wss4j.common.WSEncryptionPart;
39  import org.apache.wss4j.common.ext.Attachment;
40  import org.apache.wss4j.common.ext.AttachmentRequestCallback;
41  import org.apache.wss4j.common.ext.WSSecurityException;
42  import org.apache.wss4j.common.util.AttachmentUtils;
43  import org.apache.wss4j.common.util.XMLUtils;
44  import org.apache.wss4j.dom.WSConstants;
45  import org.apache.wss4j.dom.WSDocInfo;
46  import org.apache.wss4j.dom.callback.DOMCallbackLookup;
47  import org.apache.wss4j.dom.transform.AttachmentTransformParameterSpec;
48  import org.apache.wss4j.dom.transform.STRTransform;
49  import org.apache.wss4j.dom.util.SignatureUtils;
50  import org.apache.wss4j.dom.util.WSSecurityUtil;
51  import org.w3c.dom.Document;
52  import org.w3c.dom.Element;
53  
54  /**
55   * This is the base class for WS Security messages that are used for signature generation or
56   * verification.
57   */
58  public class WSSecSignatureBase extends WSSecBase {
59  
60      private static final org.slf4j.Logger LOG =
61          org.slf4j.LoggerFactory.getLogger(WSSecSignatureBase.class);
62  
63      private List<Element> clonedElements = new ArrayList<>();
64  
65      public WSSecSignatureBase(WSSecHeader securityHeader) {
66          super(securityHeader);
67      }
68  
69      public WSSecSignatureBase(Document doc) {
70          super(doc);
71      }
72  
73      /**
74       * This method adds references to the Signature.
75       *
76       * @param doc The parent document
77       * @param references The list of references to sign
78       * @param wsDocInfo The WSDocInfo object to store protection elements in
79       * @param signatureFactory The XMLSignature object
80       * @param addInclusivePrefixes Whether to add inclusive prefixes or not
81       * @param digestAlgo The digest algorithm to use
82       * @throws WSSecurityException
83       */
84      public List<javax.xml.crypto.dsig.Reference> addReferencesToSign(
85          Document doc,
86          List<WSEncryptionPart> references,
87          WSDocInfo wsDocInfo,
88          XMLSignatureFactory signatureFactory,
89          boolean addInclusivePrefixes,
90          String digestAlgo
91      ) throws WSSecurityException {
92          DigestMethod digestMethod;
93          try {
94              digestMethod = signatureFactory.newDigestMethod(digestAlgo, null);
95          } catch (Exception ex) {
96              LOG.error("", ex);
97              throw new WSSecurityException(
98                  WSSecurityException.ErrorCode.FAILED_SIGNATURE, ex, "noXMLSig"
99              );
100         }
101 
102         //create separate list for attachment and append it after same document references
103         //are processed.
104         List<javax.xml.crypto.dsig.Reference> attachmentReferenceList = null;
105         List<javax.xml.crypto.dsig.Reference> referenceList = new ArrayList<>();
106 
107         for (WSEncryptionPart encPart : references) {
108             String idToSign = encPart.getId();
109             String elemName = encPart.getName();
110             Element element = encPart.getElement();
111 
112             //
113             // Set up the elements to sign. There is one reserved element
114             // names: "STRTransform": Setup the ds:Reference to use STR Transform
115             //
116             try {
117                 if ("cid:Attachments".equals(idToSign) && attachmentReferenceList == null) {
118                     attachmentReferenceList =
119                         addAttachmentReferences(encPart, digestMethod, signatureFactory);
120                     continue;
121                 }
122                 if (idToSign != null) {
123                     Transform transform = null;
124                     if ("STRTransform".equals(elemName)) {
125                         Element ctx = createSTRParameter(doc);
126 
127                         XMLStructure structure = new DOMStructure(ctx);
128                         transform =
129                             signatureFactory.newTransform(
130                                 STRTransform.TRANSFORM_URI,
131                                 structure
132                             );
133                     } else {
134                         TransformParameterSpec transformSpec = null;
135                         if (element == null) {
136                             if (callbackLookup == null) {
137                                 callbackLookup = new DOMCallbackLookup(doc);
138                             }
139                             element = callbackLookup.getElement(idToSign, null, false);
140                         }
141                         if (addInclusivePrefixes && element != null) {
142                             List<String> prefixes = getInclusivePrefixes(element);
143                             if (!prefixes.isEmpty()) {
144                                 transformSpec = new ExcC14NParameterSpec(prefixes);
145                             }
146                         }
147                         transform =
148                             signatureFactory.newTransform(
149                                 WSConstants.C14N_EXCL_OMIT_COMMENTS,
150                                 transformSpec
151                             );
152                     }
153                     if (element != null) {
154                         cloneElement(element);
155 
156                         wsDocInfo.addTokenElement(element, false);
157                     } else if (!encPart.isRequired()) {
158                         continue;
159                     }
160                     javax.xml.crypto.dsig.Reference reference =
161                         signatureFactory.newReference(
162                             "#" + idToSign,
163                             digestMethod,
164                             Collections.singletonList(transform),
165                             null,
166                             null
167                         );
168                     referenceList.add(reference);
169                 } else {
170                     String nmSpace = encPart.getNamespace();
171                     List<Element> elementsToSign = null;
172                     if (element != null) {
173                         elementsToSign = Collections.singletonList(element);
174                     } else {
175                         if (callbackLookup == null) {
176                             callbackLookup = new DOMCallbackLookup(doc);
177                         }
178                         elementsToSign = WSSecurityUtil.findElements(encPart, callbackLookup);
179                     }
180                     if (elementsToSign == null || elementsToSign.isEmpty()) {
181                         if (!encPart.isRequired()) {
182                             continue;
183                         }
184                         throw new WSSecurityException(
185                             WSSecurityException.ErrorCode.FAILURE,
186                             "noEncElement",
187                             new Object[] {nmSpace + ", " + elemName});
188                     }
189                     for (Element elementToSign : elementsToSign) {
190                         String wsuId = setWsuId(elementToSign);
191 
192                         cloneElement(elementToSign);
193 
194                         TransformParameterSpec transformSpec = null;
195                         if (addInclusivePrefixes) {
196                             List<String> prefixes = getInclusivePrefixes(elementToSign);
197                             if (!prefixes.isEmpty()) {
198                                 transformSpec = new ExcC14NParameterSpec(prefixes);
199                             }
200                         }
201                         Transform transform =
202                             signatureFactory.newTransform(
203                                 WSConstants.C14N_EXCL_OMIT_COMMENTS,
204                                 transformSpec
205                             );
206                         javax.xml.crypto.dsig.Reference reference =
207                             signatureFactory.newReference(
208                                 "#" + wsuId,
209                                 digestMethod,
210                                 Collections.singletonList(transform),
211                                 null,
212                                 null
213                             );
214                         referenceList.add(reference);
215                         wsDocInfo.addTokenElement(elementToSign, false);
216                     }
217                 }
218             } catch (Exception ex) {
219                 LOG.error("", ex);
220                 throw new WSSecurityException(
221                     WSSecurityException.ErrorCode.FAILED_SIGNATURE, ex, "noXMLSig"
222                 );
223             }
224         }
225 
226         //append attachment references now
227         if (attachmentReferenceList != null) {
228             referenceList.addAll(attachmentReferenceList);
229         }
230         return referenceList;
231     }
232 
233     private void cloneElement(Element element) throws WSSecurityException {
234         if (expandXopInclude) {
235             // Look for xop:Include Nodes
236             List<Element> includeElements =
237                 XMLUtils.findElements(element.getFirstChild(), "Include", WSConstants.XOP_NS);
238             if (includeElements != null && !includeElements.isEmpty()) {
239                 // Clone the Element to be signed + insert the clone into the tree at the same level
240                 // We will expand the xop:Include for one of the nodes + sign that (and then remove it),
241                 // while leaving the original in the tree to be sent in the message
242 
243                 clonedElements.add(element);
244                 Document doc = this.getSecurityHeader().getSecurityHeaderDoc();
245                 element.getParentNode().appendChild(WSSecurityUtil.cloneElement(doc, element));
246                 WSSecurityUtil.inlineAttachments(includeElements, attachmentCallbackHandler, false);
247             }
248         }
249     }
250 
251     private List<javax.xml.crypto.dsig.Reference> addAttachmentReferences(
252         WSEncryptionPart encPart,
253         DigestMethod digestMethod,
254         XMLSignatureFactory signatureFactory
255     ) throws WSSecurityException {
256 
257         if (attachmentCallbackHandler == null) {
258             throw new WSSecurityException(
259                 WSSecurityException.ErrorCode.FAILURE,
260                 "empty", new Object[] {"no attachment callbackhandler supplied"}
261             );
262         }
263 
264         AttachmentRequestCallback attachmentRequestCallback = new AttachmentRequestCallback();
265         //no mime type must be set for signature:
266         //attachmentCallback.setResultingMimeType(null);
267         String id = AttachmentUtils.getAttachmentId(encPart.getId());
268         attachmentRequestCallback.setAttachmentId(id);
269         try {
270             attachmentCallbackHandler.handle(new Callback[]{attachmentRequestCallback});
271         } catch (Exception e) {
272             throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, e);
273         }
274 
275         List<javax.xml.crypto.dsig.Reference> attachmentReferenceList = new ArrayList<>();
276         if (attachmentRequestCallback.getAttachments() != null) {
277             for (Attachment attachment : attachmentRequestCallback.getAttachments()) {
278                 try {
279                     List<Transform> transforms = new ArrayList<>();
280 
281                     AttachmentTransformParameterSpec attachmentTransformParameterSpec =
282                         new AttachmentTransformParameterSpec(
283                             attachmentCallbackHandler, attachment
284                         );
285 
286                     String attachmentSignatureTransform = WSConstants.SWA_ATTACHMENT_CONTENT_SIG_TRANS;
287                     if ("Element".equals(encPart.getEncModifier())) {
288                         attachmentSignatureTransform = WSConstants.SWA_ATTACHMENT_COMPLETE_SIG_TRANS;
289                     }
290 
291                     transforms.add(
292                         signatureFactory.newTransform(
293                             attachmentSignatureTransform, attachmentTransformParameterSpec)
294                         );
295 
296                     javax.xml.crypto.dsig.Reference reference =
297                         signatureFactory.newReference(
298                             "cid:" + attachment.getId(), digestMethod, transforms, null, null
299                         );
300 
301                     attachmentReferenceList.add(reference);
302                 } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException e) {
303                     throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, e);
304                 }
305             }
306         }
307 
308         return attachmentReferenceList;
309     }
310 
311     /**
312      * Get the List of inclusive prefixes from the DOM Element argument
313      */
314     public List<String> getInclusivePrefixes(Element target) {
315         return getInclusivePrefixes(target, true);
316     }
317 
318 
319     /**
320      * Get the List of inclusive prefixes from the DOM Element argument
321      */
322     public List<String> getInclusivePrefixes(Element target, boolean excludeVisible) {
323         return SignatureUtils.getInclusivePrefixes(target, excludeVisible);
324     }
325 
326     /**
327      * Create an STRTransformationParameters element
328      */
329     public Element createSTRParameter(Document doc) {
330         Element transformParam =
331             doc.createElementNS(
332                 WSConstants.WSSE_NS,
333                 WSConstants.WSSE_PREFIX + ":TransformationParameters"
334             );
335 
336         Element canonElem =
337             doc.createElementNS(
338                 WSConstants.SIG_NS,
339                 WSConstants.SIG_PREFIX + ":CanonicalizationMethod"
340             );
341 
342         canonElem.setAttributeNS(null, "Algorithm", WSConstants.C14N_EXCL_OMIT_COMMENTS);
343         transformParam.appendChild(canonElem);
344         return transformParam;
345     }
346 
347     protected void cleanup() {
348         if (!clonedElements.isEmpty()) {
349             for (Element clonedElement : clonedElements) {
350                 clonedElement.getParentNode().removeChild(clonedElement);
351             }
352             clonedElements.clear();
353         }
354     }
355 
356 }