View Javadoc

1   /*
2    * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.opensaml.common.impl;
18  
19  import java.util.HashSet;
20  import java.util.List;
21  import java.util.Set;
22  
23  import org.apache.xml.security.signature.XMLSignature;
24  import org.apache.xml.security.signature.XMLSignatureException;
25  import org.apache.xml.security.transforms.Transform;
26  import org.apache.xml.security.transforms.TransformationException;
27  import org.apache.xml.security.transforms.Transforms;
28  import org.apache.xml.security.transforms.params.InclusiveNamespaces;
29  import org.opensaml.common.SignableSAMLObject;
30  import org.opensaml.xml.Configuration;
31  import org.opensaml.xml.Namespace;
32  import org.opensaml.xml.XMLObject;
33  import org.opensaml.xml.security.SecurityConfiguration;
34  import org.opensaml.xml.signature.ContentReference;
35  import org.opensaml.xml.signature.SignatureConstants;
36  import org.opensaml.xml.util.DatatypeHelper;
37  import org.opensaml.xml.util.LazyList;
38  import org.slf4j.Logger;
39  import org.slf4j.LoggerFactory;
40  
41  /**
42   * A content reference for SAML objects that will be signed. The reference is created per the SAML specification. 
43   * 
44   * <p>
45   * The default digest algorithm used is the value configured in the global security configuration's
46   * {@link SecurityConfiguration#getSignatureReferenceDigestMethod()}, if available, otherwise
47   * it will be {@link SignatureConstants#ALGO_ID_DIGEST_SHA1}.
48   * </p>
49   * 
50   * <p>
51   * The default set of transforms applied consists of {@link SignatureConstants#TRANSFORM_ENVELOPED_SIGNATURE}
52   * and {@link SignatureConstants#TRANSFORM_C14N_EXCL_WITH_COMMENTS}.
53   * </p>
54   * 
55   * <p>
56   * When generating an exclusive canonicalization transform, an inclusive namespace list is 
57   * generated from the namespaces, retrieved from {@link org.opensaml.xml.XMLObject#getNamespaces()},
58   * used by the SAML object to be signed and all of it's descendants.
59   * </p>
60   * 
61   * <p>
62   * Note that the SAML specification states that:
63   *   1) an exclusive canonicalization transform (either with or without comments) SHOULD be used.
64   *   2) transforms other than enveloped signature and one of the two exclusive canonicalizations
65   *      SHOULD NOT be used.
66   * Careful consideration should be made before deviating from these recommendations.
67   * </p>
68   * 
69   */
70  public class SAMLObjectContentReference implements ContentReference {
71  
72      /** Class logger. */
73      private final Logger log = LoggerFactory.getLogger(SAMLObjectContentReference.class);
74  
75      /** SAMLObject this reference refers to. */
76      private SignableSAMLObject signableObject;
77      
78      /** Algorithm used to digest the content. */
79      private String digestAlgorithm;
80  
81      /** Transforms applied to the content. */
82      private List<String> transforms;
83  
84      /**
85       * Constructor.
86       * 
87       * @param newSignableObject the SAMLObject this reference refers to
88       */
89      public SAMLObjectContentReference(SignableSAMLObject newSignableObject) {
90          signableObject = newSignableObject;
91          transforms = new LazyList<String>();
92          
93          // Set defaults
94          if (Configuration.getGlobalSecurityConfiguration() != null ) {
95              digestAlgorithm = Configuration.getGlobalSecurityConfiguration().getSignatureReferenceDigestMethod();
96          }
97          if (digestAlgorithm == null) {
98              digestAlgorithm = SignatureConstants.ALGO_ID_DIGEST_SHA1;
99          }
100         
101         transforms.add(SignatureConstants.TRANSFORM_ENVELOPED_SIGNATURE);
102         transforms.add(SignatureConstants.TRANSFORM_C14N_EXCL_OMIT_COMMENTS);
103     }
104     
105     /**
106      * Gets the transforms applied to the content prior to digest generation.
107      * 
108      * @return the transforms applied to the content prior to digest generation
109      */
110     public List<String> getTransforms() {
111         return transforms;
112     }
113 
114     /**
115      * Gets the algorithm used to digest the content.
116      * 
117      * @return the algorithm used to digest the content
118      */
119     public String getDigestAlgorithm() {
120         return digestAlgorithm;
121     }
122 
123     /**
124      * Sets the algorithm used to digest the content.
125      * 
126      * @param newAlgorithm the algorithm used to digest the content
127      */
128     public void setDigestAlgorithm(String newAlgorithm) {
129         digestAlgorithm = newAlgorithm;
130     }
131 
132     /** {@inheritDoc} */
133     public void createReference(XMLSignature signature) {
134         try {
135             Transforms dsigTransforms = new Transforms(signature.getDocument());
136             for (int i=0; i<transforms.size(); i++) {
137                 String transform = transforms.get(i);
138                 dsigTransforms.addTransform(transform);
139                 
140                 if (transform.equals(SignatureConstants.TRANSFORM_C14N_EXCL_WITH_COMMENTS) ||
141                     transform.equals(SignatureConstants.TRANSFORM_C14N_EXCL_OMIT_COMMENTS)) {
142                     
143                     processExclusiveTransform(signature, dsigTransforms.item(i));
144                     
145                 }
146             }
147 
148             if ( ! DatatypeHelper.isEmpty(signableObject.getSignatureReferenceID()) ) {
149                 signature.addDocument("#" + signableObject.getSignatureReferenceID(), dsigTransforms, digestAlgorithm);
150             } else {
151                 log.debug("SignableSAMLObject had no reference ID, signing using whole document Reference URI");
152                 signature.addDocument("" , dsigTransforms, digestAlgorithm);
153             }
154             
155         } catch (TransformationException e) {
156             log.error("Unsupported signature transformation", e);
157         } catch (XMLSignatureException e) {
158             log.error("Error adding content reference to signature", e);
159         }
160     }
161 
162     /**
163      * Populate the inclusive namspace prefixes on the specified Apache (exclusive) transform object.
164      * 
165      * @param signature the Apache XMLSignature object
166      * @param transform the Apache Transform object representing an exclusive transform
167      */
168     private void processExclusiveTransform(XMLSignature signature, Transform transform) {
169         // Namespaces that aren't visibly used, such as those used in QName attribute values, would
170         // be stripped out by exclusive canonicalization. Need to make sure they aren't by explicitly
171         // telling the transformer about them.
172         log.debug("Adding list of inclusive namespaces for signature exclusive canonicalization transform");
173         HashSet<String> inclusiveNamespacePrefixes = new HashSet<String>();
174         populateNamespacePrefixes(inclusiveNamespacePrefixes, signableObject);
175         
176         if (inclusiveNamespacePrefixes != null && inclusiveNamespacePrefixes.size() > 0) {
177             InclusiveNamespaces inclusiveNamespaces = new InclusiveNamespaces(signature.getDocument(),
178                     inclusiveNamespacePrefixes);
179             transform.getElement().appendChild(inclusiveNamespaces.getElement());
180         }
181     }
182 
183     /**
184      * Populates the given set with all the namespaces used by the given XMLObject and all of its descendants.
185      * 
186      * @param namespacePrefixes the namespace prefix set to be populated
187      * @param signatureContent the XMLObject whose namespace prefixes will be used to populate the set
188      */
189     private void populateNamespacePrefixes(Set<String> namespacePrefixes, XMLObject signatureContent) {
190         if (signatureContent.getNamespaces() != null) {
191             for (Namespace namespace : signatureContent.getNamespaces()) {
192                 if (namespace != null) {
193                     String namespacePrefix = namespace.getNamespacePrefix();
194                     // For the default namespace prefix, exclusive c14n uses the special token "#default".
195                     // Apache xmlsec requires this to be represented in the set with the
196                     // (completely undocumented) string "xmlns".
197                     if (namespacePrefix == null) {
198                         namespacePrefix = "xmlns";
199                     }
200                     namespacePrefixes.add(namespacePrefix);
201                 }
202             }
203         }
204 
205         if (signatureContent.getOrderedChildren() != null) {
206             for (XMLObject xmlObject : signatureContent.getOrderedChildren()) {
207                 if (xmlObject != null) {
208                     populateNamespacePrefixes(namespacePrefixes, xmlObject);
209                 }
210             }
211         }
212     }
213 }