View Javadoc

1   /*
2    * Copyright [2007] [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.security;
18  
19  import org.apache.xml.security.exceptions.XMLSecurityException;
20  import org.apache.xml.security.signature.Reference;
21  import org.apache.xml.security.signature.XMLSignature;
22  import org.apache.xml.security.transforms.Transform;
23  import org.apache.xml.security.transforms.TransformationException;
24  import org.apache.xml.security.transforms.Transforms;
25  import org.opensaml.common.SignableSAMLObject;
26  import org.opensaml.xml.signature.Signature;
27  import org.opensaml.xml.signature.impl.SignatureImpl;
28  import org.opensaml.xml.util.DatatypeHelper;
29  import org.opensaml.xml.validation.ValidationException;
30  import org.opensaml.xml.validation.Validator;
31  import org.slf4j.Logger;
32  import org.slf4j.LoggerFactory;
33  
34  /**
35   * A validator for instances of {@link Signature}, which validates that the signature meets security-related
36   * requirements indicated by the SAML profile of XML Signature.
37   */
38  public class SAMLSignatureProfileValidator implements Validator<Signature> {
39  
40      /** Class logger. */
41      private final Logger log = LoggerFactory.getLogger(SAMLSignatureProfileValidator.class);
42  
43      /** {@inheritDoc} */
44      public void validate(Signature signature) throws ValidationException {
45  
46          if (!(signature instanceof SignatureImpl)) {
47              log.info("Signature was not an instance of SignatureImpl, was {} validation not supported", signature
48                      .getClass().getName());
49              return;
50          }
51  
52          validateSignatureImpl((SignatureImpl) signature);
53      }
54  
55      /**
56       * Validate an instance of {@link SignatureImpl}, which is in turn based on underlying Apache XML Security
57       * <code>XMLSignature</code> instance.
58       * 
59       * @param sigImpl the signature implementation object to validate
60       * @throws ValidationException thrown if the signature is not valid with respect to the profile
61       */
62      protected void validateSignatureImpl(SignatureImpl sigImpl) throws ValidationException {
63  
64          if (sigImpl.getXMLSignature() == null) {
65              log.error("SignatureImpl did not contain the an Apache XMLSignature child");
66              throw new ValidationException("Apache XMLSignature does not exist on SignatureImpl");
67          }
68          XMLSignature apacheSig = sigImpl.getXMLSignature();
69  
70          if (!(sigImpl.getParent() instanceof SignableSAMLObject)) {
71              log.error("Signature is not an immedidate child of a SignableSAMLObject");
72              throw new ValidationException("Signature is not an immediate child of a SignableSAMLObject.");
73          }
74          SignableSAMLObject signableObject = (SignableSAMLObject) sigImpl.getParent();
75  
76          Reference ref = validateReference(apacheSig);
77  
78          String uri = ref.getURI();
79          String id = signableObject.getSignatureReferenceID();
80  
81          validateReferenceURI(uri, id);
82  
83          validateTransforms(ref);
84      }
85  
86      /**
87       * Validate the Signature's SignedInfo Reference.
88       * 
89       * The SignedInfo must contain exactly 1 Reference.
90       * 
91       * @param apacheSig the Apache XML Signature instance
92       * @return the valid Reference contained within the SignedInfo
93       * @throws ValidationException thrown if the Signature does not contain exactly 1 Reference, or if there is an error
94       *             obtaining the Reference instance
95       */
96      protected Reference validateReference(XMLSignature apacheSig) throws ValidationException {
97          int numReferences = apacheSig.getSignedInfo().getLength();
98          if (numReferences != 1) {
99              log.error("Signature SignedInfo had invalid number of References: " + numReferences);
100             throw new ValidationException("Signature SignedInfo must have exactly 1 Reference element");
101         }
102 
103         Reference ref = null;
104         try {
105             ref = apacheSig.getSignedInfo().item(0);
106         } catch (XMLSecurityException e) {
107             log.error("Apache XML Security exception obtaining Reference", e);
108             throw new ValidationException("Could not obtain Reference from Signature/SignedInfo", e);
109         }
110         if (ref == null) {
111             log.error("Signature Reference was null");
112             throw new ValidationException("Signature Reference was null");
113         }
114         return ref;
115     }
116 
117     /**
118      * Validate the Reference URI and parent ID attribute values.
119      * 
120      * The URI must either be null or empty (indicating that the entire enclosing document was signed), or else it must
121      * be a local document fragment reference and point to the SAMLObject parent via the latter's ID attribute value.
122      * 
123      * @param uri the Signature Reference URI attribute value
124      * @param id the Signature parents ID attribute value
125      * @throws ValidationException thrown if the URI or ID attribute values are invalid
126      */
127     protected void validateReferenceURI(String uri, String id) throws ValidationException {
128         if (!DatatypeHelper.isEmpty(uri)) {
129             if (!uri.startsWith("#")) {
130                 log.error("Signature Reference URI was not a document fragment reference: " + uri);
131                 throw new ValidationException("Signature Reference URI was not a document fragment reference");
132             } else if (DatatypeHelper.isEmpty(id)) {
133                 log.error("SignableSAMLObject did not contain an ID attribute");
134                 throw new ValidationException("SignableSAMLObject did not contain an ID attribute");
135             } else if (uri.length() < 2 || !id.equals(uri.substring(1))) {
136                 log
137                         .error(String.format("Reference URI '%s' did not point to SignableSAMLObject with ID '%s'",
138                                 uri, id));
139                 throw new ValidationException("Reference URI did not point to parent ID");
140             }
141         }
142     }
143 
144     /**
145      * Validate the transforms included in the Signature Reference.
146      * 
147      * The Reference may contain at most 2 transforms. One of them must be the Enveloped signature transform. An
148      * Exclusive Canonicalization transform (with or without comments) may also be present. No other transforms are
149      * allowed.
150      * 
151      * @param reference the Signature reference containing the transforms to evaluate
152      * @throws ValidationException thrown if the set of transforms is invalid
153      */
154     protected void validateTransforms(Reference reference) throws ValidationException {
155         Transforms transforms = null;
156         try {
157             transforms = reference.getTransforms();
158         } catch (XMLSecurityException e) {
159             log.error("Apache XML Security error obtaining Transforms instance", e);
160             throw new ValidationException("Apache XML Security error obtaining Transforms instance", e);
161         }
162 
163         if (transforms == null) {
164             log.error("Error obtaining Transforms instance, null was returned");
165             throw new ValidationException("Transforms instance was null");
166         }
167 
168         int numTransforms = transforms.getLength();
169         if (numTransforms > 2) {
170             log.error("Invalid number of Transforms was present: " + numTransforms);
171             throw new ValidationException("Invalid number of transforms");
172         }
173 
174         boolean sawEnveloped = false;
175         for (int i = 0; i < numTransforms; i++) {
176             Transform transform = null;
177             try {
178                 transform = transforms.item(i);
179             } catch (TransformationException e) {
180                 log.error("Error obtaining transform instance", e);
181                 throw new ValidationException("Error obtaining transform instance", e);
182             }
183             String uri = transform.getURI();
184             if (Transforms.TRANSFORM_ENVELOPED_SIGNATURE.equals(uri)) {
185                 log.debug("Saw Enveloped signature transform");
186                 sawEnveloped = true;
187             } else if (Transforms.TRANSFORM_C14N_EXCL_OMIT_COMMENTS.equals(uri)
188                     || Transforms.TRANSFORM_C14N_EXCL_WITH_COMMENTS.equals(uri)) {
189                 log.debug("Saw Exclusive C14N signature transform");
190             } else {
191                 log.error("Saw invalid signature transform: " + uri);
192                 throw new ValidationException("Signature contained an invalid transform");
193             }
194         }
195 
196         if (!sawEnveloped) {
197             log.error("Signature was missing the required Enveloped signature transform");
198             throw new ValidationException("Transforms did not contain the required enveloped transform");
199         }
200     }
201 
202 }