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.validate;
21  
22  import java.security.cert.X509Certificate;
23  import java.util.Collections;
24  import java.util.List;
25  
26  import javax.security.auth.callback.CallbackHandler;
27  
28  import org.apache.wss4j.common.bsp.BSPRule;
29  import org.apache.wss4j.common.crypto.Crypto;
30  import org.apache.wss4j.common.crypto.CryptoFactory;
31  import org.apache.wss4j.common.crypto.CryptoType;
32  import org.apache.wss4j.common.ext.WSSecurityException;
33  import org.apache.wss4j.common.saml.SAMLCallback;
34  import org.apache.wss4j.common.saml.SAMLUtil;
35  import org.apache.wss4j.common.saml.SamlAssertionWrapper;
36  import org.apache.wss4j.common.token.BinarySecurity;
37  import org.apache.wss4j.common.token.X509Security;
38  import org.apache.wss4j.common.util.SOAPUtil;
39  import org.apache.wss4j.common.util.XMLUtils;
40  import org.apache.wss4j.dom.WSConstants;
41  import org.apache.wss4j.dom.common.SAML1CallbackHandler;
42  
43  import org.apache.wss4j.dom.common.UsernamePasswordCallbackHandler;
44  import org.apache.wss4j.dom.engine.WSSConfig;
45  import org.apache.wss4j.dom.engine.WSSecurityEngine;
46  import org.apache.wss4j.dom.engine.WSSecurityEngineResult;
47  import org.apache.wss4j.dom.handler.RequestData;
48  import org.apache.wss4j.dom.handler.WSHandlerResult;
49  import org.apache.wss4j.dom.message.WSSecHeader;
50  import org.apache.wss4j.dom.message.WSSecSignature;
51  import org.apache.wss4j.dom.message.WSSecTimestamp;
52  import org.apache.wss4j.dom.message.WSSecUsernameToken;
53  import org.apache.wss4j.dom.util.WSSecurityUtil;
54  
55  import org.junit.jupiter.api.Test;
56  import org.w3c.dom.Document;
57  
58  import static org.junit.jupiter.api.Assertions.assertNotNull;
59  import static org.junit.jupiter.api.Assertions.assertTrue;
60  import static org.junit.jupiter.api.Assertions.fail;
61  
62  /**
63   * A test-case for Validators, check for non-standard behaviour by plugging in
64   * Validator implementations.
65   */
66  public class ValidatorTest {
67      private static final org.slf4j.Logger LOG =
68          org.slf4j.LoggerFactory.getLogger(ValidatorTest.class);
69      private WSSecurityEngine secEngine = new WSSecurityEngine();
70  
71      /**
72       * This is a test for processing an expired Timestamp.
73       */
74      @Test
75      public void testExpiredTimestamp() throws Exception {
76  
77          Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
78          WSSecHeader secHeader = new WSSecHeader(doc);
79          secHeader.insertSecurityHeader();
80  
81          WSSecTimestamp timestamp = new WSSecTimestamp(secHeader);
82          timestamp.setTimeToLive(-1);
83          Document createdDoc = timestamp.build();
84  
85          if (LOG.isDebugEnabled()) {
86              String outputString =
87                  XMLUtils.prettyDocumentToString(createdDoc);
88              LOG.debug(outputString);
89          }
90  
91          // The default behaviour is that the Timestamp validation will fail
92          WSSConfig wssConfig = WSSConfig.getNewInstance();
93          try {
94              verify(createdDoc, wssConfig, null, null);
95              fail("Expected failure on an expired timestamp");
96          } catch (WSSecurityException ex) {
97              assertTrue(ex.getErrorCode() == WSSecurityException.ErrorCode.MESSAGE_EXPIRED);
98          }
99  
100         // Now switch out the default Timestamp validator
101         wssConfig.setValidator(WSConstants.TIMESTAMP, NoOpValidator.class);
102         verify(createdDoc, wssConfig, null, null);
103     }
104 
105     /**
106      * Test for processing an untrusted signature
107      */
108     @Test
109     public void testUntrustedSignature() throws Exception {
110         Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
111         WSSecHeader secHeader = new WSSecHeader(doc);
112         secHeader.insertSecurityHeader();
113 
114         WSSecSignature sign = new WSSecSignature(secHeader);
115         sign.setUserInfo("wss40", "security");
116         sign.setKeyIdentifierType(WSConstants.X509_KEY_IDENTIFIER);
117 
118         Crypto crypto = CryptoFactory.getInstance("wss40.properties");
119         Document signedDoc = sign.build(crypto);
120 
121         if (LOG.isDebugEnabled()) {
122             String outputString =
123                 XMLUtils.prettyDocumentToString(signedDoc);
124             LOG.debug(outputString);
125         }
126 
127         // The default behaviour is that trust verification will fail
128         Crypto cryptoCA = CryptoFactory.getInstance("crypto.properties");
129         // Turn off BSP spec compliance
130         WSSecurityEngine newEngine = new WSSecurityEngine();
131         RequestData data = new RequestData();
132         data.setSigVerCrypto(cryptoCA);
133         data.setIgnoredBSPRules(Collections.singletonList(BSPRule.R3063));
134         try {
135             newEngine.processSecurityHeader(signedDoc, data);
136             fail("Failure expected on issuer serial");
137         } catch (WSSecurityException ex) {
138             assertTrue(ex.getErrorCode() == WSSecurityException.ErrorCode.FAILURE);
139         }
140 
141         // Now switch out the default signature validator
142         WSSConfig config = WSSConfig.getNewInstance();
143         config.setValidator(WSConstants.SIGNATURE, NoOpValidator.class);
144         newEngine.setWssConfig(config);
145         data.setWssConfig(config);
146         newEngine.processSecurityHeader(signedDoc, data);
147     }
148 
149     /**
150      * Test that adds a UserNameToken with (bad) password text to a WS-Security envelope
151      */
152     @Test
153     public void testUsernameTokenBadText() throws Exception {
154         Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
155         WSSecHeader secHeader = new WSSecHeader(doc);
156         secHeader.insertSecurityHeader();
157 
158         WSSecUsernameToken builder = new WSSecUsernameToken(secHeader);
159         builder.setPasswordType(WSConstants.PASSWORD_TEXT);
160         builder.setUserInfo("wernerd", "verySecre");
161 
162         Document signedDoc = builder.build();
163 
164         if (LOG.isDebugEnabled()) {
165             String outputString =
166                 XMLUtils.prettyDocumentToString(signedDoc);
167             LOG.debug(outputString);
168         }
169 
170         // The default behaviour is that password verification will fail
171         WSSConfig wssConfig = WSSConfig.getNewInstance();
172         try {
173             verify(signedDoc, wssConfig, new UsernamePasswordCallbackHandler(), null);
174             fail("Failure expected on a bad password text");
175         } catch (WSSecurityException ex) {
176             assertTrue(ex.getErrorCode() == WSSecurityException.ErrorCode.FAILED_AUTHENTICATION);
177         }
178 
179         // Now switch out the default UsernameToken validator
180         wssConfig.setValidator(WSConstants.USERNAME_TOKEN, NoOpValidator.class);
181         verify(signedDoc, wssConfig, new UsernamePasswordCallbackHandler(), null);
182     }
183 
184     /**
185      * In this test, a BinarySecurityToken is added to the SOAP header. A custom processor
186      * validates the BST and transforms it into a SAML Assertion.
187      */
188     @Test
189     public void testTransformedBST() throws Exception {
190         Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
191 
192         WSSecHeader secHeader = new WSSecHeader(doc);
193         secHeader.insertSecurityHeader();
194 
195         X509Security bst = new X509Security(doc);
196         CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS);
197         cryptoType.setAlias("wss40");
198         Crypto crypto = CryptoFactory.getInstance("wss40.properties");
199         X509Certificate[] certs = crypto.getX509Certificates(cryptoType);
200         bst.setX509Certificate(certs[0]);
201 
202         WSSecurityUtil.prependChildElement(secHeader.getSecurityHeaderElement(), bst.getElement());
203 
204         if (LOG.isDebugEnabled()) {
205             LOG.debug("BST output");
206             String outputString =
207                 XMLUtils.prettyDocumentToString(doc);
208             LOG.debug(outputString);
209         }
210 
211         WSSConfig config = WSSConfig.getNewInstance();
212         config.setValidator(WSConstants.BINARY_TOKEN, new BSTValidator());
213         WSSecurityEngine secEngine = new WSSecurityEngine();
214         secEngine.setWssConfig(config);
215         WSHandlerResult results =
216             secEngine.processSecurityHeader(doc, null, null, crypto);
217 
218         List<WSSecurityEngineResult> bstResults =
219             results.getActionResults().get(WSConstants.BST);
220         WSSecurityEngineResult actionResult = bstResults.get(0);
221 
222         BinarySecurity token =
223             (BinarySecurity)actionResult.get(WSSecurityEngineResult.TAG_BINARY_SECURITY_TOKEN);
224         assertNotNull(token);
225 
226         SamlAssertionWrapper samlAssertion =
227             (SamlAssertionWrapper)actionResult.get(WSSecurityEngineResult.TAG_TRANSFORMED_TOKEN);
228         assertNotNull(samlAssertion);
229     }
230 
231     /**
232      * In this test, a SOAP request is constructed where the SOAP body is signed via a
233      * BinarySecurityToken. The receiving side does not trust the BST, and so the test fails.
234      * The second time, a custom Validator (NoOpValidator for this case) is installed for the
235      * BST, and so trust verification passes on the Signature.
236      */
237     @Test
238     public void testValidatedBSTSignature() throws Exception {
239         Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
240         WSSecHeader secHeader = new WSSecHeader(doc);
241         secHeader.insertSecurityHeader();
242 
243         WSSecSignature builder = new WSSecSignature(secHeader);
244         builder.setUserInfo("16c73ab6-b892-458f-abf5-2f875f74882e", "security");
245         builder.setKeyIdentifierType(WSConstants.BST_DIRECT_REFERENCE);
246         Document signedDoc = builder.build(CryptoFactory.getInstance());
247 
248         if (LOG.isDebugEnabled()) {
249             String outputString =
250                 XMLUtils.prettyDocumentToString(signedDoc);
251             LOG.debug(outputString);
252         }
253 
254         Crypto crypto = CryptoFactory.getInstance("wss40.properties");
255         WSSConfig config = WSSConfig.getNewInstance();
256         WSSecurityEngine secEngine = new WSSecurityEngine();
257         secEngine.setWssConfig(config);
258         try {
259             secEngine.processSecurityHeader(doc, null, null, crypto);
260             fail("Expected failure on untrusted signature");
261         } catch (WSSecurityException ex) {
262             assertTrue(ex.getErrorCode() == WSSecurityException.ErrorCode.FAILURE);
263         }
264 
265         config.setValidator(WSConstants.BINARY_TOKEN, new BSTValidator());
266         WSHandlerResult results =
267             secEngine.processSecurityHeader(doc, null, null, crypto);
268 
269         List<WSSecurityEngineResult> bstResults =
270             results.getActionResults().get(WSConstants.BST);
271         WSSecurityEngineResult actionResult = bstResults.get(0);
272 
273         BinarySecurity token =
274             (BinarySecurity)actionResult.get(WSSecurityEngineResult.TAG_BINARY_SECURITY_TOKEN);
275         assertNotNull(token);
276     }
277 
278 
279     /**
280      * Verifies the soap envelope
281      *
282      * @param doc soap document
283      * @param wssConfig
284      * @throws Exception Thrown when there is a problem in verification
285      */
286     private WSHandlerResult verify(
287         Document doc, WSSConfig wssConfig, CallbackHandler cb, Crypto crypto
288     ) throws Exception {
289         secEngine.setWssConfig(wssConfig);
290         return secEngine.processSecurityHeader(doc, null, cb, crypto);
291     }
292 
293 
294     /**
295      * A validator for a BST token.
296      */
297     private static class BSTValidator implements Validator {
298 
299         public Credential validate(Credential credential, RequestData data) throws WSSecurityException {
300             BinarySecurity token = credential.getBinarySecurityToken();
301             if (token == null) {
302                 throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE);
303             }
304 
305             try {
306                 SAML1CallbackHandler callbackHandler = new SAML1CallbackHandler();
307                 callbackHandler.setStatement(SAML1CallbackHandler.Statement.AUTHN);
308                 callbackHandler.setIssuer("www.example.com");
309 
310                 SAMLCallback samlCallback = new SAMLCallback();
311                 SAMLUtil.doSAMLCallback(callbackHandler, samlCallback);
312                 SamlAssertionWrapper samlAssertion = new SamlAssertionWrapper(samlCallback);
313 
314                 credential.setTransformedToken(samlAssertion);
315                 return credential;
316             } catch (Exception ex) {
317                 throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE);
318             }
319         }
320 
321     }
322 
323 
324 }