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.ws.security.saml;
21  
22  import org.apache.ws.security.WSConstants;
23  import org.apache.ws.security.WSDerivedKeyTokenPrincipal;
24  import org.apache.ws.security.WSDocInfo;
25  import org.apache.ws.security.WSPasswordCallback;
26  import org.apache.ws.security.WSSecurityEngine;
27  import org.apache.ws.security.WSSecurityEngineResult;
28  import org.apache.ws.security.WSSecurityException;
29  import org.apache.ws.security.components.crypto.AlgorithmSuite;
30  import org.apache.ws.security.components.crypto.AlgorithmSuiteValidator;
31  import org.apache.ws.security.components.crypto.CryptoType;
32  import org.apache.ws.security.handler.RequestData;
33  import org.apache.ws.security.message.token.SecurityTokenReference;
34  import org.apache.ws.security.processor.EncryptedKeyProcessor;
35  import org.apache.ws.security.processor.Processor;
36  import org.apache.ws.security.saml.ext.AssertionWrapper;
37  import org.apache.ws.security.str.STRParser;
38  import org.apache.ws.security.str.SignatureSTRParser;
39  import org.apache.ws.security.util.Base64;
40  import org.apache.ws.security.util.WSSecurityUtil;
41  
42  import org.opensaml.saml2.core.SubjectConfirmationData;
43  import org.w3c.dom.Element;
44  import org.w3c.dom.Node;
45  import org.w3c.dom.Text;
46  
47  import javax.security.auth.callback.Callback;
48  import javax.security.auth.callback.CallbackHandler;
49  import javax.xml.crypto.XMLStructure;
50  import javax.xml.crypto.dom.DOMStructure;
51  import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
52  import javax.xml.crypto.dsig.keyinfo.KeyValue;
53  import javax.xml.crypto.dsig.keyinfo.X509Data;
54  import javax.xml.crypto.dsig.keyinfo.X509IssuerSerial;
55  import javax.xml.namespace.QName;
56  
57  import java.security.NoSuchProviderException;
58  import java.security.Principal;
59  import java.security.PublicKey;
60  import java.security.cert.X509Certificate;
61  import java.util.HashMap;
62  import java.util.List;
63  
64  /**
65   * Utility methods for SAML stuff
66   */
67  public final class SAMLUtil {
68      
69      private static final QName BINARY_SECRET = 
70          new QName(WSConstants.WST_NS, "BinarySecret");
71      private static final QName BINARY_SECRET_05_12 = 
72          new QName(WSConstants.WST_NS_05_12, "BinarySecret");
73      
74      private SAMLUtil() {
75          // Complete
76      }
77  
78      /**
79       * Get an AssertionWrapper object from parsing a SecurityTokenReference that uses
80       * a KeyIdentifier that points to a SAML Assertion.
81       * 
82       * @param secRef the SecurityTokenReference to the SAML Assertion
83       * @param strElement The SecurityTokenReference DOM element
84       * @param request The RequestData instance used to obtain configuration
85       * @param wsDocInfo The WSDocInfo object that holds previous results
86       * @return an AssertionWrapper object
87       * @throws WSSecurityException
88       */
89      public static AssertionWrapper getAssertionFromKeyIdentifier(
90          SecurityTokenReference secRef,
91          Element strElement,
92          RequestData request,
93          WSDocInfo wsDocInfo
94      ) throws WSSecurityException {
95          String keyIdentifierValue = secRef.getKeyIdentifierValue();
96          String type = secRef.getKeyIdentifierValueType();
97          WSSecurityEngineResult result = wsDocInfo.getResult(keyIdentifierValue);
98  
99          AssertionWrapper assertion = null;
100         Element token = null;
101         if (result != null) {
102             assertion = 
103                 (AssertionWrapper)result.get(WSSecurityEngineResult.TAG_SAML_ASSERTION);
104             return assertion;
105         } else {
106             token = 
107                 secRef.findProcessedTokenElement(
108                     strElement.getOwnerDocument(), wsDocInfo,
109                     request.getCallbackHandler(),
110                     keyIdentifierValue, type
111                 );
112             if (token != null) {
113                 if (!"Assertion".equals(token.getLocalName())) {
114                     throw new WSSecurityException(
115                         WSSecurityException.FAILURE, "invalidSAMLsecurity"
116                     );
117                 }
118                 return new AssertionWrapper(token);
119             }
120             token = 
121                 secRef.findUnprocessedTokenElement(
122                     strElement.getOwnerDocument(), wsDocInfo,
123                     request.getCallbackHandler(), keyIdentifierValue, type
124                 );
125             
126             if (token == null || !"Assertion".equals(token.getLocalName())) {
127                 throw new WSSecurityException(
128                     WSSecurityException.FAILURE, "invalidSAMLsecurity"
129                 );
130             }
131             Processor proc = request.getWssConfig().getProcessor(WSSecurityEngine.SAML_TOKEN);
132             List<WSSecurityEngineResult> samlResult =
133                 proc.handleToken(token, request, wsDocInfo);
134             return 
135                 (AssertionWrapper)samlResult.get(0).get(
136                     WSSecurityEngineResult.TAG_SAML_ASSERTION
137                 );
138         }
139     }
140     
141     /**
142      * Parse a SAML Assertion to obtain a SAMLKeyInfo object from
143      * the Subject of the assertion
144      * 
145      * @param assertion The SAML Assertion
146      * @param data The RequestData instance used to obtain configuration
147      * @param docInfo A WSDocInfo instance
148      * @param bspCompliant Whether to process tokens in compliance with the BSP spec or not
149      * @return a SAMLKeyInfo object
150      * @throws WSSecurityException
151      */
152     public static SAMLKeyInfo getCredentialFromSubject(
153         AssertionWrapper assertion, 
154         RequestData data,
155         WSDocInfo docInfo,
156         boolean bspCompliant
157     ) throws WSSecurityException {
158         if (assertion.getSaml1() != null) {
159             return getCredentialFromSubject(assertion.getSaml1(), data, docInfo, bspCompliant);
160         } else {
161             return getCredentialFromSubject(assertion.getSaml2(), data, docInfo, bspCompliant);
162         }
163     }
164     
165     /**
166      * Try to get the secret key from a CallbackHandler implementation
167      * @param cb a CallbackHandler implementation
168      * @return An array of bytes corresponding to the secret key (can be null)
169      * @throws WSSecurityException
170      */
171     private static byte[] getSecretKeyFromCallbackHandler(
172         String id,
173         CallbackHandler cb
174     ) throws WSSecurityException {
175         if (cb != null) {
176             WSPasswordCallback pwcb = 
177                 new WSPasswordCallback(id, WSPasswordCallback.SECRET_KEY);
178             try {
179                 cb.handle(new Callback[]{pwcb});
180             } catch (Exception e1) {
181                 throw new WSSecurityException(WSSecurityException.FAILURE, "noKey",
182                         new Object[] { id }, e1);
183             }
184             return pwcb.getKey();
185         }
186         return null;
187     }
188     
189     /**
190      * Get the SAMLKeyInfo object corresponding to the credential stored in the Subject of a 
191      * SAML 1.1 assertion
192      * @param assertion The SAML 1.1 assertion
193      * @param data The RequestData instance used to obtain configuration
194      * @param docInfo A WSDocInfo instance
195      * @param bspCompliant Whether to process tokens in compliance with the BSP spec or not
196      * @return The SAMLKeyInfo object obtained from the Subject
197      * @throws WSSecurityException
198      */
199     public static SAMLKeyInfo getCredentialFromSubject(
200         org.opensaml.saml1.core.Assertion assertion,
201         RequestData data,
202         WSDocInfo docInfo,
203         boolean bspCompliant
204     ) throws WSSecurityException {
205         // First try to get the credential from a CallbackHandler
206         byte[] key = getSecretKeyFromCallbackHandler(assertion.getID(), data.getCallbackHandler());
207         if (key != null && key.length > 0) {
208             return new SAMLKeyInfo(key);
209         }
210         
211         for (org.opensaml.saml1.core.Statement stmt : assertion.getStatements()) {
212             org.opensaml.saml1.core.Subject samlSubject = null;
213             if (stmt instanceof org.opensaml.saml1.core.AttributeStatement) {
214                 org.opensaml.saml1.core.AttributeStatement attrStmt = 
215                     (org.opensaml.saml1.core.AttributeStatement) stmt;
216                 samlSubject = attrStmt.getSubject();
217             } else if (stmt instanceof org.opensaml.saml1.core.AuthenticationStatement) {
218                 org.opensaml.saml1.core.AuthenticationStatement authStmt = 
219                     (org.opensaml.saml1.core.AuthenticationStatement) stmt;
220                 samlSubject = authStmt.getSubject();
221             } else {
222                 org.opensaml.saml1.core.AuthorizationDecisionStatement authzStmt =
223                     (org.opensaml.saml1.core.AuthorizationDecisionStatement)stmt;
224                 samlSubject = authzStmt.getSubject();
225             }
226             
227             if (samlSubject == null) {
228                 throw new WSSecurityException(
229                     WSSecurityException.FAILURE, "invalidSAMLToken", 
230                     new Object[] {"for Signature (no Subject)"}
231                 );
232             }
233 
234             Element sub = samlSubject.getSubjectConfirmation().getDOM();
235             Element keyInfoElement = 
236                 WSSecurityUtil.getDirectChildElement(sub, "KeyInfo", WSConstants.SIG_NS);
237             if (keyInfoElement != null) {
238                 return getCredentialFromKeyInfo(keyInfoElement, data, docInfo, bspCompliant);
239             }
240         }
241 
242         return null;
243     }
244     
245     /**
246      * Get the SAMLKeyInfo object corresponding to the credential stored in the Subject of a 
247      * SAML 2 assertion
248      * @param assertion The SAML 2 assertion
249      * @param data The RequestData instance used to obtain configuration
250      * @param docInfo A WSDocInfo instance
251      * @param bspCompliant Whether to process tokens in compliance with the BSP spec or not
252      * @return The SAMLKeyInfo object obtained from the Subject
253      * @throws WSSecurityException
254      */
255     public static SAMLKeyInfo getCredentialFromSubject(
256         org.opensaml.saml2.core.Assertion assertion,
257         RequestData data,
258         WSDocInfo docInfo,
259         boolean bspCompliant
260     ) throws WSSecurityException {
261         // First try to get the credential from a CallbackHandler
262         byte[] key = getSecretKeyFromCallbackHandler(assertion.getID(), data.getCallbackHandler());
263         if (key != null && key.length > 0) {
264             return new SAMLKeyInfo(key);
265         }
266         
267         org.opensaml.saml2.core.Subject samlSubject = assertion.getSubject();
268         if (samlSubject == null) {
269             throw new WSSecurityException(
270                 WSSecurityException.FAILURE, "invalidSAMLToken", 
271                 new Object[]{"for Signature (no Subject)"}
272             );
273         }
274         List<org.opensaml.saml2.core.SubjectConfirmation> subjectConfList = 
275             samlSubject.getSubjectConfirmations();
276         for (org.opensaml.saml2.core.SubjectConfirmation subjectConfirmation : subjectConfList) {
277             SubjectConfirmationData subjConfData = 
278                 subjectConfirmation.getSubjectConfirmationData();
279             Element sub = subjConfData.getDOM();
280             Element keyInfoElement = 
281                 WSSecurityUtil.getDirectChildElement(sub, "KeyInfo", WSConstants.SIG_NS);
282             if (keyInfoElement != null) {
283                 return getCredentialFromKeyInfo(keyInfoElement, data, docInfo, bspCompliant);
284             }
285         }
286 
287         return null;
288     }
289     
290     /**
291      * This method returns a SAMLKeyInfo corresponding to the credential found in the
292      * KeyInfo (DOM Element) argument.
293      * @param keyInfoElement The KeyInfo as a DOM Element
294      * @param data The RequestData instance used to obtain configuration
295      * @param docInfo A WSDocInfo instance
296      * @param bspCompliant Whether to process tokens in compliance with the BSP spec or not
297      * @return The credential (as a SAMLKeyInfo object)
298      * @throws WSSecurityException
299      */
300     public static SAMLKeyInfo getCredentialFromKeyInfo(
301         Element keyInfoElement,
302         RequestData data,
303         WSDocInfo docInfo,
304         boolean bspCompliant
305     ) throws WSSecurityException {
306         //
307         // First try to find an EncryptedKey, BinarySecret or a SecurityTokenReference via DOM
308         //
309         Node node = keyInfoElement.getFirstChild();
310         while (node != null) {
311             if (Node.ELEMENT_NODE == node.getNodeType()) {
312                 QName el = new QName(node.getNamespaceURI(), node.getLocalName());
313                 if (el.equals(WSSecurityEngine.ENCRYPTED_KEY)) {
314                     EncryptedKeyProcessor proc = new EncryptedKeyProcessor();
315                     List<WSSecurityEngineResult> result =
316                         proc.handleToken((Element)node, data, docInfo, data.getSamlAlgorithmSuite());
317                     byte[] secret = 
318                         (byte[])result.get(0).get(
319                             WSSecurityEngineResult.TAG_SECRET
320                         );
321                     return new SAMLKeyInfo(secret);
322                 } else if (el.equals(BINARY_SECRET) || el.equals(BINARY_SECRET_05_12)) {
323                     Text txt = (Text)node.getFirstChild();
324                     return new SAMLKeyInfo(Base64.decode(txt.getData()));
325                 } else if (SecurityTokenReference.STR_QNAME.equals(el)) {
326                     STRParser strParser = new SignatureSTRParser();
327                     strParser.parseSecurityTokenReference(
328                         (Element)node, data, docInfo, new HashMap<String, Object>()
329                     );
330                     SAMLKeyInfo samlKeyInfo = new SAMLKeyInfo(strParser.getCertificates());
331                     samlKeyInfo.setPublicKey(strParser.getPublicKey());
332                     samlKeyInfo.setSecret(strParser.getSecretKey());
333                     
334                     Principal principal = strParser.getPrincipal();
335                     
336                     // Check for compliance against the defined AlgorithmSuite
337                     AlgorithmSuite algorithmSuite = data.getSamlAlgorithmSuite(); 
338                     if (algorithmSuite != null && principal instanceof WSDerivedKeyTokenPrincipal) {
339                         AlgorithmSuiteValidator algorithmSuiteValidator = new
340                             AlgorithmSuiteValidator(algorithmSuite);
341 
342                         algorithmSuiteValidator.checkDerivedKeyAlgorithm(
343                             ((WSDerivedKeyTokenPrincipal)principal).getAlgorithm()
344                         );
345                         algorithmSuiteValidator.checkSignatureDerivedKeyLength(
346                             ((WSDerivedKeyTokenPrincipal)principal).getLength()
347                         );
348                     }
349                     
350                     return samlKeyInfo;
351                 }
352             }
353             node = node.getNextSibling();
354         }
355         
356         return getCredentialDirectlyFromKeyInfo(keyInfoElement, data);
357     }
358         
359     /**
360      * This method returns a SAMLKeyInfo corresponding to the credential found in the
361      * KeyInfo (DOM Element) argument.
362      * @param keyInfoElement The KeyInfo as a DOM Element
363      * @param data The RequestData instance used to obtain configuration
364      * @return The credential (as a SAMLKeyInfo object)
365      * @throws WSSecurityException
366      */
367     public static SAMLKeyInfo getCredentialDirectlyFromKeyInfo(
368         Element keyInfoElement,
369         RequestData data
370     ) throws WSSecurityException {
371         //
372         // Next marshal the KeyInfo DOM element into a javax KeyInfo object and get the
373         // (public key) credential
374         //
375         X509Certificate[] certs = null;
376         KeyInfoFactory keyInfoFactory = null;
377         try {
378             keyInfoFactory = KeyInfoFactory.getInstance("DOM", "ApacheXMLDSig");
379         } catch (NoSuchProviderException ex) {
380             keyInfoFactory = KeyInfoFactory.getInstance("DOM");
381         }
382         XMLStructure keyInfoStructure = new DOMStructure(keyInfoElement);
383 
384         try {
385             javax.xml.crypto.dsig.keyinfo.KeyInfo keyInfo = 
386                 keyInfoFactory.unmarshalKeyInfo(keyInfoStructure);
387             List<?> list = keyInfo.getContent();
388 
389             for (int i = 0; i < list.size(); i++) {
390                 XMLStructure xmlStructure = (XMLStructure) list.get(i);
391                 if (xmlStructure instanceof KeyValue) {
392                     PublicKey publicKey = ((KeyValue)xmlStructure).getPublicKey();
393                     return new SAMLKeyInfo(publicKey);
394                 } else if (xmlStructure instanceof X509Data) {
395                     List<?> x509Data = ((X509Data)xmlStructure).getContent();
396                     for (int j = 0; j < x509Data.size(); j++) {
397                         Object x509obj = x509Data.get(j);
398                         if (x509obj instanceof X509Certificate) {
399                             certs = new X509Certificate[1];
400                             certs[0] = (X509Certificate)x509obj;
401                             return new SAMLKeyInfo(certs);
402                         } else if (x509obj instanceof X509IssuerSerial) {
403                             if (data.getSigCrypto() == null) {
404                                 throw new WSSecurityException(
405                                     WSSecurityException.FAILURE, "noSigCryptoFile"
406                                 );
407                             }
408                             CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ISSUER_SERIAL);
409                             cryptoType.setIssuerSerial(
410                                 ((X509IssuerSerial)x509obj).getIssuerName(), 
411                                 ((X509IssuerSerial)x509obj).getSerialNumber()
412                             );
413                             certs = data.getSigCrypto().getX509Certificates(cryptoType);
414                             if (certs == null || certs.length < 1) {
415                                 throw new WSSecurityException(
416                                     WSSecurityException.FAILURE, "invalidSAMLsecurity",
417                                     new Object[]{"cannot get certificate or key"}
418                                 );
419                             }
420                             return new SAMLKeyInfo(certs);
421                         }
422                     }
423                 }
424             }
425         } catch (Exception ex) {
426             throw new WSSecurityException(
427                 WSSecurityException.FAILURE, "invalidSAMLsecurity",
428                 new Object[]{"cannot get certificate or key"}, ex
429             );
430         }
431         return null;
432     }
433 
434 }