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