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.common.saml;
21  
22  import javax.xml.namespace.QName;
23  
24  import net.shibboleth.shared.xml.impl.BasicParserPool;
25  import net.shibboleth.shared.xml.ParserPool;
26  
27  import org.apache.wss4j.common.crypto.WSProviderConfig;
28  import org.apache.wss4j.common.ext.WSSecurityException;
29  import org.opensaml.core.config.Configuration;
30  import org.opensaml.core.config.ConfigurationService;
31  import org.opensaml.core.config.provider.MapBasedConfiguration;
32  import org.opensaml.core.xml.XMLObject;
33  import org.opensaml.core.xml.XMLObjectBuilder;
34  import org.opensaml.core.xml.XMLObjectBuilderFactory;
35  import org.opensaml.core.xml.config.XMLConfigurationException;
36  import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
37  import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
38  import org.opensaml.core.xml.io.Marshaller;
39  import org.opensaml.core.xml.io.MarshallerFactory;
40  import org.opensaml.core.xml.io.MarshallingException;
41  import org.opensaml.core.xml.io.Unmarshaller;
42  import org.opensaml.core.xml.io.UnmarshallerFactory;
43  import org.opensaml.core.xml.io.UnmarshallingException;
44  import org.opensaml.saml.common.SignableSAMLObject;
45  import org.opensaml.saml.config.SAMLConfiguration;
46  import org.opensaml.xmlsec.config.DecryptionParserPool;
47  import org.opensaml.xmlsec.signature.Signature;
48  import org.opensaml.xmlsec.signature.support.SignatureException;
49  import org.opensaml.xmlsec.signature.support.Signer;
50  import org.opensaml.xmlsec.signature.support.SignerProvider;
51  import org.w3c.dom.Document;
52  import org.w3c.dom.DocumentFragment;
53  import org.w3c.dom.Element;
54  
55  /**
56   * Class OpenSAMLUtil provides static helper methods for the OpenSaml library
57   */
58  public final class OpenSAMLUtil {
59      private static final org.slf4j.Logger LOG =
60          org.slf4j.LoggerFactory.getLogger(OpenSAMLUtil.class);
61  
62      private static XMLObjectProviderRegistry providerRegistry;
63      private static XMLObjectBuilderFactory builderFactory;
64      private static MarshallerFactory marshallerFactory;
65      private static UnmarshallerFactory unmarshallerFactory;
66      private static boolean samlEngineInitialized = false;
67  
68      private OpenSAMLUtil() {
69          // Complete
70      }
71  
72      /**
73       * Initialise the SAML library
74       */
75      public static synchronized void initSamlEngine() {
76          initSamlEngine(true);
77      }
78  
79      public static synchronized void initSamlEngine(boolean includeXacml) {
80          if (!samlEngineInitialized) {
81              LOG.debug("Initializing the opensaml2 library...");
82              WSProviderConfig.init();
83  
84              Configuration configuration = new MapBasedConfiguration();
85              ConfigurationService.setConfiguration(configuration);
86  
87              providerRegistry = new XMLObjectProviderRegistry();
88              configuration.register(XMLObjectProviderRegistry.class, providerRegistry,
89                                     ConfigurationService.DEFAULT_PARTITION_NAME);
90  
91              try {
92                  OpenSAMLBootstrap.bootstrap(includeXacml);
93  
94                  SAMLConfiguration samlConfiguration = new SAMLConfiguration();
95  
96                  configuration.register(SAMLConfiguration.class, samlConfiguration,
97                                         ConfigurationService.DEFAULT_PARTITION_NAME);
98  
99                  builderFactory = XMLObjectProviderRegistrySupport.getBuilderFactory();
100                 marshallerFactory = XMLObjectProviderRegistrySupport.getMarshallerFactory();
101                 unmarshallerFactory = XMLObjectProviderRegistrySupport.getUnmarshallerFactory();
102 
103                 try {
104                     configureParserPool();
105 
106                     // used by org.opensaml.saml.saml2.encryption.Decrypter
107                     configuration.register(DecryptionParserPool.class, new DecryptionParserPool(getParserPool()),
108                         ConfigurationService.DEFAULT_PARTITION_NAME);
109                 } catch (Throwable t) {
110                     LOG.warn("Unable to bootstrap the parser pool part of the opensaml library "
111                              + "- some SAML operations may fail", t);
112                 }
113 
114                 samlEngineInitialized = true;
115                 LOG.debug("opensaml3 library bootstrap complete");
116             } catch (XMLConfigurationException ex) {
117                 LOG.error("Unable to bootstrap the opensaml3 library - all SAML operations will fail", ex);
118             }
119         }
120     }
121 
122     private static void configureParserPool() throws Throwable {
123         BasicParserPool pp = new BasicParserPool();
124         pp.setMaxPoolSize(50);
125         pp.initialize();
126         providerRegistry.setParserPool(pp);
127     }
128 
129     /**
130      * Get the configured ParserPool.
131      *
132      * @return the configured ParserPool
133      */
134     public static ParserPool getParserPool() {
135         return providerRegistry.getParserPool();
136     }
137 
138     /**
139      * Convert a SAML Assertion from a DOM Element to an XMLObject
140      *
141      * @param root of type Element
142      * @return XMLObject
143      * @throws WSSecurityException
144      */
145     public static XMLObject fromDom(Element root) throws WSSecurityException {
146         if (root == null) {
147             LOG.debug("Attempting to unmarshal a null element!");
148             throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "empty",
149                                           new Object[] {"Error unmarshalling a SAML assertion"});
150         }
151         Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(root);
152         if (unmarshaller == null) {
153             LOG.debug("Unable to find an unmarshaller for element: " + root.getLocalName());
154             throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "empty",
155                                           new Object[] {"Error unmarshalling a SAML assertion"});
156         }
157         try {
158             return unmarshaller.unmarshall(root);
159         } catch (UnmarshallingException ex) {
160             throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, ex, "empty",
161                                           new Object[] {"Error unmarshalling a SAML assertion"});
162         }
163     }
164 
165     /**
166      * Convert a SAML Assertion from a XMLObject to a DOM Element
167      *
168      * @param xmlObject of type XMLObject
169      * @param doc  of type Document
170      * @return Element
171      * @throws WSSecurityException
172      */
173     public static Element toDom(
174         XMLObject xmlObject,
175         Document doc
176     ) throws WSSecurityException {
177         return toDom(xmlObject, doc, true);
178     }
179 
180     /**
181      * Convert a SAML Assertion from a XMLObject to a DOM Element
182      *
183      * @param xmlObject of type XMLObject
184      * @param doc  of type Document
185      * @param signObject whether to sign the XMLObject during marshalling
186      * @return Element
187      * @throws WSSecurityException
188      */
189     public static Element toDom(
190         XMLObject xmlObject,
191         Document doc,
192         boolean signObject
193     ) throws WSSecurityException {
194         Marshaller marshaller = marshallerFactory.getMarshaller(xmlObject);
195         Element element = null;
196         DocumentFragment frag = doc == null ? null : doc.createDocumentFragment();
197         try {
198             if (frag != null) {
199                 while (doc.getFirstChild() != null) {
200                     frag.appendChild(doc.removeChild(doc.getFirstChild()));
201                 }
202             }
203             try {
204                 if (doc == null) {
205                     element = marshaller.marshall(xmlObject);
206                 } else {
207                     element = marshaller.marshall(xmlObject, doc);
208                 }
209             } catch (MarshallingException ex) {
210                 throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, ex, "empty",
211                                               new Object[] {"Error marshalling a SAML assertion"});
212             }
213 
214             if (signObject) {
215                 signXMLObject(xmlObject);
216             }
217         } finally {
218             if (frag != null) {
219                 while (doc.getFirstChild() != null) {
220                     doc.removeChild(doc.getFirstChild());
221                 }
222                 doc.appendChild(frag);
223             }
224         }
225         return element;
226     }
227 
228     private static void signXMLObject(XMLObject xmlObject) throws WSSecurityException {
229         if (xmlObject instanceof org.opensaml.saml.saml1.core.Response) {
230             org.opensaml.saml.saml1.core.Response response =
231                     (org.opensaml.saml.saml1.core.Response)xmlObject;
232 
233             // Sign any Assertions
234             if (response.getAssertions() != null) {
235                 for (org.opensaml.saml.saml1.core.Assertion assertion : response.getAssertions()) {
236                     signObject(assertion.getSignature());
237                 }
238             }
239 
240             signObject(response.getSignature());
241         } else if (xmlObject instanceof org.opensaml.saml.saml2.core.Response) {
242             org.opensaml.saml.saml2.core.Response response =
243                     (org.opensaml.saml.saml2.core.Response)xmlObject;
244 
245             // Sign any Assertions
246             if (response.getAssertions() != null) {
247                 for (org.opensaml.saml.saml2.core.Assertion assertion : response.getAssertions()) {
248                     signObject(assertion.getSignature());
249                 }
250             }
251 
252             signObject(response.getSignature());
253         } else if (xmlObject instanceof SignableSAMLObject) {
254             signObject(((SignableSAMLObject)xmlObject).getSignature());
255         }
256     }
257 
258     private static void signObject(Signature signature) throws WSSecurityException {
259         if (signature != null) {
260             ClassLoader loader = Thread.currentThread().getContextClassLoader();
261             try {
262                 Thread.currentThread().setContextClassLoader(SignerProvider.class.getClassLoader());
263                 Signer.signObject(signature);
264             } catch (SignatureException ex) {
265                 throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, ex, "empty",
266                                               new Object[] {"Error signing a SAML assertion"});
267             } finally {
268                 Thread.currentThread().setContextClassLoader(loader);
269             }
270         }
271     }
272 
273     /**
274      * Method buildSignature ...
275      *
276      * @return Signature
277      */
278     @SuppressWarnings("unchecked")
279     public static Signature buildSignature() {
280         QName qName = Signature.DEFAULT_ELEMENT_NAME;
281         XMLObjectBuilder<Signature> builder =
282             (XMLObjectBuilder<Signature>)builderFactory.getBuilder(qName);
283         if (builder == null) {
284             LOG.error(
285                 "Unable to retrieve builder for object QName "
286                 + qName
287             );
288             return null;
289         }
290         return
291             builder.buildObject(
292                  qName.getNamespaceURI(), qName.getLocalPart(), qName.getPrefix()
293              );
294     }
295 
296     /**
297      * Method isMethodSenderVouches ...
298      *
299      * @param confirmMethod of type String
300      * @return boolean
301      */
302     public static boolean isMethodSenderVouches(String confirmMethod) {
303         return
304             confirmMethod != null && confirmMethod.startsWith("urn:oasis:names:tc:SAML:")
305                 && confirmMethod.endsWith(":cm:sender-vouches");
306     }
307 
308     /**
309      * Method isMethodHolderOfKey ...
310      *
311      * @param confirmMethod of type String
312      * @return boolean
313      */
314     public static boolean isMethodHolderOfKey(String confirmMethod) {
315         return
316             confirmMethod != null && confirmMethod.startsWith("urn:oasis:names:tc:SAML:")
317                 && confirmMethod.endsWith(":cm:holder-of-key");
318     }
319 
320 }