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.dom.processor;
21  
22  import java.security.NoSuchProviderException;
23  import java.security.Provider;
24  import java.security.PublicKey;
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.List;
28  
29  import javax.xml.crypto.MarshalException;
30  import javax.xml.crypto.dsig.Reference;
31  import javax.xml.crypto.dsig.Transform;
32  import javax.xml.crypto.dsig.XMLSignature;
33  import javax.xml.crypto.dsig.XMLSignatureFactory;
34  import javax.xml.crypto.dsig.XMLValidateContext;
35  import javax.xml.crypto.dsig.dom.DOMValidateContext;
36  import javax.xml.namespace.QName;
37  
38  import org.apache.wss4j.common.crypto.AlgorithmSuite;
39  import org.apache.wss4j.common.crypto.AlgorithmSuiteValidator;
40  import org.apache.wss4j.common.ext.WSSecurityException;
41  import org.apache.wss4j.common.principal.SAMLTokenPrincipalImpl;
42  import org.apache.wss4j.common.saml.SAMLKeyInfo;
43  import org.apache.wss4j.common.saml.SAMLUtil;
44  import org.apache.wss4j.common.saml.SamlAssertionWrapper;
45  import org.apache.wss4j.common.util.DOM2Writer;
46  import org.apache.wss4j.dom.WSConstants;
47  import org.apache.wss4j.dom.WSDataRef;
48  import org.apache.wss4j.dom.engine.WSSecurityEngineResult;
49  import org.apache.wss4j.dom.handler.RequestData;
50  import org.apache.wss4j.dom.saml.WSSSAMLKeyInfoProcessor;
51  import org.apache.wss4j.dom.util.EncryptionUtils;
52  import org.apache.wss4j.dom.validate.Credential;
53  import org.apache.wss4j.dom.validate.Validator;
54  import org.opensaml.xmlsec.signature.KeyInfo;
55  import org.opensaml.xmlsec.signature.Signature;
56  import org.w3c.dom.Element;
57  
58  public class SAMLTokenProcessor implements Processor {
59      private static final org.slf4j.Logger LOG =
60          org.slf4j.LoggerFactory.getLogger(SAMLTokenProcessor.class);
61      private XMLSignatureFactory signatureFactory;
62  
63      public SAMLTokenProcessor() {
64          init(null);
65      }
66  
67      public SAMLTokenProcessor(Provider provider) {
68          init(provider);
69      }
70  
71      private void init(Provider provider) {
72          if (provider == null) {
73              // Try to install the Santuario Provider - fall back to the JDK provider if this does
74              // not work
75              try {
76                  signatureFactory = XMLSignatureFactory.getInstance("DOM", "ApacheXMLDSig");
77              } catch (NoSuchProviderException ex) {
78                  signatureFactory = XMLSignatureFactory.getInstance("DOM");
79              }
80          } else {
81              signatureFactory = XMLSignatureFactory.getInstance("DOM", provider);
82          }
83      }
84  
85      public List<WSSecurityEngineResult> handleToken(
86          Element elem,
87          RequestData data
88      ) throws WSSecurityException {
89          LOG.debug("Found SAML Assertion element");
90  
91          Validator validator =
92              data.getValidator(new QName(elem.getNamespaceURI(), elem.getLocalName()));
93  
94          SamlAssertionWrapper samlAssertion = new SamlAssertionWrapper(elem);
95          XMLSignature xmlSignature = verifySignatureKeysAndAlgorithms(samlAssertion, data);
96          List<WSDataRef> dataRefs = createDataRefs(elem, samlAssertion, xmlSignature);
97  
98          Credential credential = handleSAMLToken(samlAssertion, data, validator);
99          samlAssertion = credential.getSamlAssertion();
100         if (LOG.isDebugEnabled()) {
101             LOG.debug("SAML Assertion issuer " + samlAssertion.getIssuerString());
102             LOG.debug(DOM2Writer.nodeToString(elem));
103         }
104 
105         // See if the token has been previously processed
106         String id = samlAssertion.getId();
107         Element foundElement = data.getWsDocInfo().getTokenElement(id);
108         if (elem.equals(foundElement)) {
109             WSSecurityEngineResult result = data.getWsDocInfo().getResult(id);
110             return java.util.Collections.singletonList(result);
111         } else if (foundElement != null) {
112             throw new WSSecurityException(
113                 WSSecurityException.ErrorCode.INVALID_SECURITY_TOKEN, "duplicateError"
114             );
115         }
116 
117         data.getWsDocInfo().addTokenElement(elem);
118         WSSecurityEngineResult result = null;
119         if (samlAssertion.isSigned()) {
120             result = new WSSecurityEngineResult(WSConstants.ST_SIGNED, samlAssertion);
121             result.put(WSSecurityEngineResult.TAG_DATA_REF_URIS, dataRefs);
122             result.put(WSSecurityEngineResult.TAG_SIGNATURE_VALUE, samlAssertion.getSignatureValue());
123         } else {
124             result = new WSSecurityEngineResult(WSConstants.ST_UNSIGNED, samlAssertion);
125         }
126 
127         if (id.length() != 0) {
128             result.put(WSSecurityEngineResult.TAG_ID, id);
129         }
130 
131         if (validator != null) {
132             result.put(WSSecurityEngineResult.TAG_VALIDATED_TOKEN, Boolean.TRUE);
133             if (credential.getTransformedToken() != null) {
134                 result.put(
135                     WSSecurityEngineResult.TAG_TRANSFORMED_TOKEN, credential.getTransformedToken()
136                 );
137                 if (credential.getPrincipal() != null) {
138                     result.put(WSSecurityEngineResult.TAG_PRINCIPAL, credential.getPrincipal());
139                 } else {
140                     SAMLTokenPrincipalImpl samlPrincipal =
141                         new SAMLTokenPrincipalImpl(credential.getTransformedToken());
142                     result.put(WSSecurityEngineResult.TAG_PRINCIPAL, samlPrincipal);
143                 }
144             } else if (credential.getPrincipal() != null) {
145                 result.put(WSSecurityEngineResult.TAG_PRINCIPAL, credential.getPrincipal());
146             } else {
147                 result.put(WSSecurityEngineResult.TAG_PRINCIPAL, new SAMLTokenPrincipalImpl(samlAssertion));
148             }
149             result.put(WSSecurityEngineResult.TAG_SUBJECT, credential.getSubject());
150         }
151         data.getWsDocInfo().addResult(result);
152         return java.util.Collections.singletonList(result);
153     }
154 
155     public Credential handleSAMLToken(
156         SamlAssertionWrapper samlAssertion,
157         RequestData data,
158         Validator validator
159     ) throws WSSecurityException {
160         // Parse the subject if it exists
161         samlAssertion.parseSubject(
162             new WSSSAMLKeyInfoProcessor(data), data.getSigVerCrypto()
163         );
164 
165         // Now delegate the rest of the verification to the Validator
166         Credential credential = new Credential();
167         credential.setSamlAssertion(samlAssertion);
168         if (validator != null) {
169             return validator.validate(credential, data);
170         }
171         return credential;
172     }
173 
174     private XMLSignature verifySignatureKeysAndAlgorithms(
175         SamlAssertionWrapper samlAssertion,
176         RequestData data
177     ) throws WSSecurityException {
178         if (samlAssertion.isSigned()) {
179             Signature sig = samlAssertion.getSignature();
180             KeyInfo keyInfo = sig.getKeyInfo();
181             if (keyInfo == null) {
182                 throw new WSSecurityException(
183                     WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity",
184                     new Object[] {"cannot get certificate or key"}
185                 );
186             }
187             SAMLKeyInfo samlKeyInfo =
188                 SAMLUtil.getCredentialFromKeyInfo(
189                     keyInfo.getDOM(), new WSSSAMLKeyInfoProcessor(data), data.getSigVerCrypto()
190                 );
191 
192             PublicKey key = null;
193             if (samlKeyInfo.getCerts() != null && samlKeyInfo.getCerts()[0] != null) {
194                 key = samlKeyInfo.getCerts()[0].getPublicKey();
195             } else if (samlKeyInfo.getPublicKey() != null) {
196                 key = samlKeyInfo.getPublicKey();
197             } else {
198                 throw new WSSecurityException(
199                     WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity",
200                     new Object[] {"cannot get certificate or key"});
201             }
202 
203             // Not checking signature here, just marshalling into an XMLSignature
204             // structure for testing the transform/digest algorithms etc.
205             XMLValidateContext context = new DOMValidateContext(key, sig.getDOM());
206             context.setProperty("org.apache.jcp.xml.dsig.secureValidation", Boolean.TRUE);
207             context.setProperty("org.jcp.xml.dsig.secureValidation", Boolean.TRUE);
208             if (data.getSignatureProvider() != null) {
209                 context.setProperty("org.jcp.xml.dsig.internal.dom.SignatureProvider", data.getSignatureProvider());
210             }
211 
212             XMLSignature xmlSignature;
213             try {
214                 xmlSignature = signatureFactory.unmarshalXMLSignature(context);
215             } catch (MarshalException ex) {
216                 throw new WSSecurityException(
217                     WSSecurityException.ErrorCode.FAILED_CHECK, ex, "invalidSAMLsecurity",
218                     new Object[] {"cannot get certificate or key"}
219                 );
220             }
221 
222             // Check for compliance against the defined AlgorithmSuite
223             AlgorithmSuite algorithmSuite = data.getSamlAlgorithmSuite();
224             if (algorithmSuite != null) {
225                 AlgorithmSuiteValidator algorithmSuiteValidator = new
226                     AlgorithmSuiteValidator(algorithmSuite);
227 
228                 algorithmSuiteValidator.checkSignatureAlgorithms(xmlSignature);
229 
230                 if (samlKeyInfo.getCerts() != null && samlKeyInfo.getCerts().length > 0) {
231                     algorithmSuiteValidator.checkAsymmetricKeyLength(samlKeyInfo.getCerts());
232                 } else {
233                     algorithmSuiteValidator.checkAsymmetricKeyLength(key);
234                 }
235             }
236 
237             samlAssertion.verifySignature(samlKeyInfo);
238 
239             return xmlSignature;
240         }
241 
242         return null;
243     }
244 
245     private List<WSDataRef> createDataRefs(
246         Element token, SamlAssertionWrapper samlAssertion, XMLSignature xmlSignature
247     ) {
248         if (xmlSignature == null) {
249             return Collections.emptyList();
250         }
251 
252         List<WSDataRef> protectedRefs = new ArrayList<>();
253         String signatureMethod =
254             xmlSignature.getSignedInfo().getSignatureMethod().getAlgorithm();
255 
256         for (Object refObject : xmlSignature.getSignedInfo().getReferences()) {
257             Reference reference = (Reference)refObject;
258 
259             if (reference.getURI() == null || reference.getURI().length() == 0
260                 || reference.getURI().equals(samlAssertion.getId())
261                 || reference.getURI().equals("#" + samlAssertion.getId())) {
262                 WSDataRef ref = new WSDataRef();
263                 ref.setWsuId(reference.getURI());
264                 ref.setProtectedElement(token);
265                 ref.setAlgorithm(signatureMethod);
266                 ref.setDigestAlgorithm(reference.getDigestMethod().getAlgorithm());
267                 ref.setDigestValue(reference.getDigestValue());
268 
269                 // Set the Transform algorithms as well
270                 @SuppressWarnings("unchecked")
271                 List<Transform> transforms = reference.getTransforms();
272                 List<String> transformAlgorithms = new ArrayList<>(transforms.size());
273                 for (Transform transform : transforms) {
274                     transformAlgorithms.add(transform.getAlgorithm());
275                 }
276                 ref.setTransformAlgorithms(transformAlgorithms);
277 
278                 ref.setXpath(EncryptionUtils.getXPath(token));
279                 protectedRefs.add(ref);
280             }
281         }
282 
283         return protectedRefs;
284     }
285 }