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 java.io.IOException;
23  import java.security.NoSuchProviderException;
24  import java.security.PublicKey;
25  import java.security.cert.X509Certificate;
26  import java.util.List;
27  
28  import javax.security.auth.callback.CallbackHandler;
29  import javax.security.auth.callback.UnsupportedCallbackException;
30  import javax.xml.crypto.XMLStructure;
31  import javax.xml.crypto.dom.DOMStructure;
32  import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
33  import javax.xml.crypto.dsig.keyinfo.KeyValue;
34  import javax.xml.crypto.dsig.keyinfo.X509Data;
35  import javax.xml.crypto.dsig.keyinfo.X509IssuerSerial;
36  
37  import org.apache.wss4j.common.crypto.Crypto;
38  import org.apache.wss4j.common.crypto.CryptoType;
39  import org.apache.wss4j.common.ext.WSSecurityException;
40  import org.apache.wss4j.common.util.XMLUtils;
41  import org.opensaml.saml.saml2.core.SubjectConfirmationData;
42  import org.w3c.dom.Element;
43  
44  /**
45   * Utility methods for SAML stuff
46   */
47  public final class SAMLUtil {
48  
49      /**
50       * This constant defines the maximum amount of child elements of a Signature KeyInfo, associated with a
51       * signed SAML assertion. Any other child element will be ignored.
52       */
53      private static final int MAX_KEYINFO_CONTENT_LIST_SIZE = 3;
54  
55      /**
56       * This constant defines the maximum amount of child elements of a X509Data KeyInfo, associated with a
57       * signed SAML assertion. Any other child element will be ignored.
58       */
59      private static final int MAX_X509DATA_SIZE = 5;
60  
61      private static final String SIG_NS = "http://www.w3.org/2000/09/xmldsig#";
62  
63      private SAMLUtil() {
64          // Complete
65      }
66  
67      /**
68       * Parse a SAML Assertion to obtain a SAMLKeyInfo object from
69       * the Subject of the assertion
70       *
71       * @param samlAssertion The SAML Assertion
72       * @param keyInfoProcessor A pluggable way to parse the KeyInfo
73       * @return a SAMLKeyInfo object
74       * @throws WSSecurityException
75       */
76      public static SAMLKeyInfo getCredentialFromSubject(
77          SamlAssertionWrapper samlAssertion,
78          SAMLKeyInfoProcessor keyInfoProcessor,
79          Crypto sigCrypto
80      ) throws WSSecurityException {
81          if (samlAssertion.getSaml1() != null) {
82              return getCredentialFromSubject(
83                  samlAssertion.getSaml1(), keyInfoProcessor, sigCrypto
84              );
85          } else if (samlAssertion.getSaml2() != null) {
86              return getCredentialFromSubject(
87                  samlAssertion.getSaml2(), keyInfoProcessor, sigCrypto
88              );
89          }
90  
91          throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "empty",
92                                        new Object[] {"Cannot get credentials from an unknown SAML Assertion"});
93      }
94  
95      /**
96       * Get the SAMLKeyInfo object corresponding to the credential stored in the Subject of a
97       * SAML 1.1 assertion
98       * @param assertion The SAML 1.1 assertion
99       * @param keyInfoProcessor A pluggable way to parse the KeyInfo
100      * @param sigCrypto A Crypto instance
101      * @return The SAMLKeyInfo object obtained from the Subject
102      * @throws WSSecurityException
103      */
104     public static SAMLKeyInfo getCredentialFromSubject(
105         org.opensaml.saml.saml1.core.Assertion assertion,
106         SAMLKeyInfoProcessor keyInfoProcessor,
107         Crypto sigCrypto
108     ) throws WSSecurityException {
109         for (org.opensaml.saml.saml1.core.Statement stmt : assertion.getStatements()) {
110             org.opensaml.saml.saml1.core.Subject samlSubject = null;
111             if (stmt instanceof org.opensaml.saml.saml1.core.AttributeStatement) {
112                 org.opensaml.saml.saml1.core.AttributeStatement attrStmt =
113                     (org.opensaml.saml.saml1.core.AttributeStatement) stmt;
114                 samlSubject = attrStmt.getSubject();
115             } else if (stmt instanceof org.opensaml.saml.saml1.core.AuthenticationStatement) {
116                 org.opensaml.saml.saml1.core.AuthenticationStatement authStmt =
117                     (org.opensaml.saml.saml1.core.AuthenticationStatement) stmt;
118                 samlSubject = authStmt.getSubject();
119             } else {
120                 org.opensaml.saml.saml1.core.AuthorizationDecisionStatement authzStmt =
121                     (org.opensaml.saml.saml1.core.AuthorizationDecisionStatement)stmt;
122                 samlSubject = authzStmt.getSubject();
123             }
124 
125             if (samlSubject != null && samlSubject.getSubjectConfirmation() != null) {
126                 Element sub = samlSubject.getSubjectConfirmation().getDOM();
127                 Element keyInfoElement =
128                     XMLUtils.getDirectChildElement(sub, "KeyInfo", SIG_NS);
129                 if (keyInfoElement != null) {
130                     return getCredentialFromKeyInfo(
131                         keyInfoElement, keyInfoProcessor, sigCrypto
132                     );
133                 }
134             }
135         }
136 
137         return null;
138     }
139 
140     /**
141      * Get the SAMLKeyInfo object corresponding to the credential stored in the Subject of a
142      * SAML 2 assertion
143      * @param assertion The SAML 2 assertion
144      * @param keyInfoProcessor A pluggable way to parse the KeyInfo
145      * @param sigCrypto A Crypto instance
146      * @return The SAMLKeyInfo object obtained from the Subject
147      * @throws WSSecurityException
148      */
149     public static SAMLKeyInfo getCredentialFromSubject(
150         org.opensaml.saml.saml2.core.Assertion assertion,
151         SAMLKeyInfoProcessor keyInfoProcessor,
152         Crypto sigCrypto
153     ) throws WSSecurityException {
154         org.opensaml.saml.saml2.core.Subject samlSubject = assertion.getSubject();
155         if (samlSubject != null) {
156             List<org.opensaml.saml.saml2.core.SubjectConfirmation> subjectConfList =
157                 samlSubject.getSubjectConfirmations();
158             for (org.opensaml.saml.saml2.core.SubjectConfirmation subjectConfirmation : subjectConfList) {
159                 SubjectConfirmationData subjConfData =
160                     subjectConfirmation.getSubjectConfirmationData();
161                 if (subjConfData != null) {
162                     Element sub = subjConfData.getDOM();
163                     Element keyInfoElement =
164                         XMLUtils.getDirectChildElement(sub, "KeyInfo", SIG_NS);
165                     if (keyInfoElement != null) {
166                         return getCredentialFromKeyInfo(
167                             keyInfoElement, keyInfoProcessor, sigCrypto
168                         );
169                     }
170                 }
171             }
172         }
173 
174         return null;
175     }
176 
177     /**
178      * This method returns a SAMLKeyInfo corresponding to the credential found in the
179      * KeyInfo (DOM Element) argument.
180      * @param keyInfoElement The KeyInfo as a DOM Element
181      * @param keyInfoProcessor A pluggable way to parse the KeyInfo
182      * @param sigCrypto A Crypto instance
183      * @return The credential (as a SAMLKeyInfo object)
184      * @throws WSSecurityException
185      */
186     public static SAMLKeyInfo getCredentialFromKeyInfo(
187         Element keyInfoElement,
188         SAMLKeyInfoProcessor keyInfoProcessor,
189         Crypto sigCrypto
190     ) throws WSSecurityException {
191         //
192         // First try to find an EncryptedKey, BinarySecret or a SecurityTokenReference via DOM
193         //
194         if (keyInfoProcessor != null) {
195             SAMLKeyInfo samlKeyInfo = keyInfoProcessor.processSAMLKeyInfo(keyInfoElement);
196             if (samlKeyInfo != null) {
197                 return samlKeyInfo;
198             }
199         }
200 
201         //
202         // Next marshal the KeyInfo DOM element into a javax KeyInfo object and get the
203         // (public key) credential
204         //
205         KeyInfoFactory keyInfoFactory = null;
206         try {
207             keyInfoFactory = KeyInfoFactory.getInstance("DOM", "ApacheXMLDSig");
208         } catch (NoSuchProviderException ex) {
209             keyInfoFactory = KeyInfoFactory.getInstance("DOM");
210         }
211         XMLStructure keyInfoStructure = new DOMStructure(keyInfoElement);
212 
213         try {
214             javax.xml.crypto.dsig.keyinfo.KeyInfo keyInfo =
215                 keyInfoFactory.unmarshalKeyInfo(keyInfoStructure);
216             List<?> list = keyInfo.getContent();
217 
218             X509Certificate[] certs = null;
219             PublicKey publicKey = null;
220             for (int i = 0; i < list.size(); i++) {
221                 // Put a hard bound on how many child elements there can be of KeyInfo
222                 if (i >= MAX_KEYINFO_CONTENT_LIST_SIZE) {
223                     break;
224                 }
225                 XMLStructure xmlStructure = (XMLStructure) list.get(i);
226                 if (xmlStructure instanceof KeyValue && publicKey == null) {
227                     publicKey = ((KeyValue)xmlStructure).getPublicKey();
228                 } else if (xmlStructure instanceof X509Data) {
229                     List<?> x509Data = ((X509Data)xmlStructure).getContent();
230                     for (int j = 0; j < x509Data.size(); j++) {
231                         // Put a hard bound on how many child elements there can be of X509Data
232                         if (j >= MAX_X509DATA_SIZE || certs != null) {
233                             break;
234                         }
235                         Object x509obj = x509Data.get(j);
236                         if (x509obj instanceof X509Certificate) {
237                             certs = new X509Certificate[1];
238                             certs[0] = (X509Certificate)x509obj;
239                         } else if (x509obj instanceof X509IssuerSerial) {
240                             if (sigCrypto == null) {
241                                 throw new WSSecurityException(
242                                     WSSecurityException.ErrorCode.FAILURE, "noSigCryptoFile"
243                                 );
244                             }
245                             CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ISSUER_SERIAL);
246                             cryptoType.setIssuerSerial(
247                                 ((X509IssuerSerial)x509obj).getIssuerName(),
248                                 ((X509IssuerSerial)x509obj).getSerialNumber()
249                             );
250                             certs = sigCrypto.getX509Certificates(cryptoType);
251                             if (certs == null || certs.length < 1) {
252                                 throw new WSSecurityException(
253                                     WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity",
254                                     new Object[] {"cannot get certificate or key"}
255                                 );
256                             }
257                         }
258                     }
259                 }
260             }
261 
262             if (certs != null || publicKey != null) {
263                 SAMLKeyInfo samlKeyInfo = new SAMLKeyInfo(certs);
264                 samlKeyInfo.setPublicKey(publicKey);
265                 return samlKeyInfo;
266             }
267         } catch (Exception ex) {
268             throw new WSSecurityException(
269                 WSSecurityException.ErrorCode.FAILURE, ex, "invalidSAMLsecurity",
270                 new Object[] {"cannot get certificate or key"}
271             );
272         }
273         return null;
274     }
275 
276     public static void doSAMLCallback(
277         CallbackHandler callbackHandler, SAMLCallback callback
278     ) {
279         // Create a new SAMLCallback with all of the information from the properties file.
280         try {
281             // Get the SAML source data using the currently configured callback implementation.
282             callbackHandler.handle(new SAMLCallback[]{callback});
283         } catch (IOException | UnsupportedCallbackException e) {
284             throw new IllegalStateException(
285                 "Error while creating SAML assertion wrapper", e
286             );
287         }
288     }
289 
290 }