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.message;
21  
22  import org.apache.wss4j.common.util.SOAPUtil;
23  import org.apache.wss4j.dom.WSConstants;
24  import org.apache.wss4j.dom.common.CustomHandler;
25  
26  import org.apache.wss4j.dom.common.UsernamePasswordCallbackHandler;
27  import org.apache.wss4j.dom.engine.WSSConfig;
28  import org.apache.wss4j.dom.engine.WSSecurityEngine;
29  import org.apache.wss4j.dom.engine.WSSecurityEngineResult;
30  import org.apache.wss4j.common.crypto.Crypto;
31  import org.apache.wss4j.common.crypto.CryptoFactory;
32  import org.apache.wss4j.common.ext.WSSecurityException;
33  import org.apache.wss4j.common.util.UsernameTokenUtil;
34  import org.apache.wss4j.common.util.XMLUtils;
35  import org.apache.wss4j.dom.handler.HandlerAction;
36  import org.apache.wss4j.dom.handler.RequestData;
37  import org.apache.wss4j.dom.handler.WSHandlerConstants;
38  import org.apache.wss4j.dom.handler.WSHandlerResult;
39  
40  import org.junit.jupiter.api.Test;
41  import org.w3c.dom.Document;
42  
43  import javax.security.auth.callback.CallbackHandler;
44  
45  import java.util.Collections;
46  
47  import static org.junit.jupiter.api.Assertions.assertFalse;
48  import static org.junit.jupiter.api.Assertions.assertTrue;
49  import static org.junit.jupiter.api.Assertions.fail;
50  
51  /**
52   * WS-Security Test Case for UsernameToken Key Derivation, as defined in the
53   * UsernameTokenProfile 1.1 specification. The derived keys are used for signature.
54   * Note that this functionality is different to the UTDerivedKeyTest test case,
55   * which uses the derived key in conjunction with wsc:DerivedKeyToken. It's also
56   * different to UTWseSignatureTest, which derives a key for signature using a
57   * non-standard implementation.
58   */
59  public class UTSignatureTest {
60      private static final org.slf4j.Logger LOG =
61          org.slf4j.LoggerFactory.getLogger(UTSignatureTest.class);
62      private CallbackHandler callbackHandler = new UsernamePasswordCallbackHandler();
63      private Crypto crypto;
64  
65      public UTSignatureTest() throws Exception {
66          crypto = CryptoFactory.getInstance();
67      }
68  
69      /**
70       * Test using a UsernameToken derived key for signing a SOAP body
71       */
72      @Test
73      public void testSignature() throws Exception {
74          Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
75          WSSecHeader secHeader = new WSSecHeader(doc);
76          secHeader.insertSecurityHeader();
77  
78          WSSecUsernameToken builder = new WSSecUsernameToken(secHeader);
79          builder.setUserInfo("bob", "security");
80          builder.addDerivedKey(1000);
81          byte[] salt = UsernameTokenUtil.generateSalt(true);
82          builder.prepare(salt);
83  
84          WSSecSignature sign = new WSSecSignature(secHeader);
85          sign.setCustomTokenValueType(WSConstants.USERNAMETOKEN_NS + "#UsernameToken");
86          sign.setCustomTokenId(builder.getId());
87          sign.setSecretKey(builder.getDerivedKey(salt));
88          sign.setKeyIdentifierType(WSConstants.CUSTOM_SYMM_SIGNING);
89          sign.setSignatureAlgorithm(WSConstants.HMAC_SHA1);
90  
91          Document signedDoc = sign.build(null);
92          builder.prependToHeader();
93  
94          String outputString =
95              XMLUtils.prettyDocumentToString(signedDoc);
96          assertTrue(outputString.contains("wsse:Username"));
97          assertFalse(outputString.contains("wsse:Password"));
98          assertTrue(outputString.contains("wsse11:Salt"));
99          assertTrue(outputString.contains("wsse11:Iteration"));
100         if (LOG.isDebugEnabled()) {
101             LOG.debug(outputString);
102         }
103 
104         WSHandlerResult results = verify(signedDoc);
105         WSSecurityEngineResult actionResult =
106             results.getActionResults().get(WSConstants.UT_SIGN).get(0);
107         java.security.Principal principal =
108             (java.security.Principal) actionResult.get(WSSecurityEngineResult.TAG_PRINCIPAL);
109         assertTrue(principal.getName().contains("bob"));
110 
111         try {
112             verify(signedDoc, false);
113             fail("Failure expected on deriving keys from a UsernameToken not allowed");
114         } catch (WSSecurityException ex) {
115             assertTrue(ex.getErrorCode() == WSSecurityException.ErrorCode.FAILED_AUTHENTICATION);
116         }
117     }
118 
119 
120     /**
121      * Test using a UsernameToken derived key for signing a SOAP body. In this test the
122      * user is "colm" rather than "bob", and so signature verification should fail.
123      */
124     @Test
125     public void testBadUserSignature() throws Exception {
126         Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
127         WSSecHeader secHeader = new WSSecHeader(doc);
128         secHeader.insertSecurityHeader();
129 
130         WSSecUsernameToken builder = new WSSecUsernameToken(secHeader);
131         builder.setUserInfo("colm", "security");
132         builder.addDerivedKey(1000);
133         byte[] salt = UsernameTokenUtil.generateSalt(true);
134         builder.prepare(salt);
135 
136         WSSecSignature sign = new WSSecSignature(secHeader);
137         sign.setCustomTokenValueType(WSConstants.USERNAMETOKEN_NS + "#UsernameToken");
138         sign.setCustomTokenId(builder.getId());
139         sign.setSecretKey(builder.getDerivedKey(salt));
140         sign.setKeyIdentifierType(WSConstants.CUSTOM_SYMM_SIGNING);
141         sign.setSignatureAlgorithm(WSConstants.HMAC_SHA1);
142 
143         Document signedDoc = sign.build(null);
144         builder.prependToHeader();
145 
146         String outputString =
147             XMLUtils.prettyDocumentToString(signedDoc);
148         if (LOG.isDebugEnabled()) {
149             LOG.debug(outputString);
150         }
151 
152         try {
153             verify(signedDoc);
154             fail("Failure expected on a bad derived signature");
155         } catch (WSSecurityException ex) {
156             assertTrue(ex.getErrorCode() == WSSecurityException.ErrorCode.FAILED_AUTHENTICATION);
157             // expected
158         }
159     }
160 
161     /**
162      * Test using a UsernameToken derived key for signing a SOAP body via WSHandler
163      */
164     @Test
165     public void testHandlerSignature() throws Exception {
166 
167         final WSSConfig cfg = WSSConfig.getNewInstance();
168         RequestData reqData = new RequestData();
169         reqData.setWssConfig(cfg);
170         java.util.Map<String, Object> messageContext = new java.util.TreeMap<>();
171         messageContext.put(WSHandlerConstants.PW_CALLBACK_REF, callbackHandler);
172         reqData.setMsgContext(messageContext);
173         reqData.setUsername("bob");
174 
175         Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
176         CustomHandler handler = new CustomHandler();
177         HandlerAction action = new HandlerAction(WSConstants.UT_SIGN);
178         handler.send(
179             doc,
180             reqData,
181             Collections.singletonList(action),
182             true
183         );
184 
185         String outputString =
186             XMLUtils.prettyDocumentToString(doc);
187         assertTrue(outputString.contains("wsse:Username"));
188         assertFalse(outputString.contains("wsse:Password"));
189         assertTrue(outputString.contains("wsse11:Salt"));
190         assertTrue(outputString.contains("wsse11:Iteration"));
191         if (LOG.isDebugEnabled()) {
192             LOG.debug(outputString);
193         }
194 
195         WSHandlerResult results = verify(doc);
196         WSSecurityEngineResult actionResult =
197             results.getActionResults().get(WSConstants.UT_SIGN).get(0);
198         java.security.Principal principal =
199             (java.security.Principal) actionResult.get(WSSecurityEngineResult.TAG_PRINCIPAL);
200         assertTrue(principal.getName().contains("bob"));
201     }
202 
203     /**
204      * Test using a UsernameToken derived key for signing a SOAP body via WSHandler
205      */
206     @Test
207     public void testHandlerSignatureIterations() throws Exception {
208 
209         final WSSConfig cfg = WSSConfig.getNewInstance();
210         RequestData reqData = new RequestData();
211         reqData.setWssConfig(cfg);
212         java.util.Map<String, Object> messageContext = new java.util.TreeMap<>();
213         messageContext.put(WSHandlerConstants.PW_CALLBACK_REF, callbackHandler);
214         messageContext.put(WSHandlerConstants.DERIVED_KEY_ITERATIONS, "1234");
215         reqData.setMsgContext(messageContext);
216         reqData.setUsername("bob");
217 
218         Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
219         CustomHandler handler = new CustomHandler();
220         HandlerAction action = new HandlerAction(WSConstants.UT_SIGN);
221         handler.send(
222             doc,
223             reqData,
224             Collections.singletonList(action),
225             true
226         );
227 
228         String outputString =
229             XMLUtils.prettyDocumentToString(doc);
230         assertTrue(outputString.contains("wsse:Username"));
231         assertFalse(outputString.contains("wsse:Password"));
232         assertTrue(outputString.contains("wsse11:Salt"));
233         assertTrue(outputString.contains("wsse11:Iteration"));
234         assertTrue(outputString.contains("1234"));
235         if (LOG.isDebugEnabled()) {
236             LOG.debug(outputString);
237         }
238 
239         WSHandlerResult results = verify(doc);
240         WSSecurityEngineResult actionResult =
241             results.getActionResults().get(WSConstants.UT_SIGN).get(0);
242         java.security.Principal principal =
243             (java.security.Principal) actionResult.get(WSSecurityEngineResult.TAG_PRINCIPAL);
244         assertTrue(principal.getName().contains("bob"));
245     }
246 
247     /**
248      * Verifies the soap envelope.
249      *
250      * @param doc soap envelope
251      * @throws Exception Thrown when there is a problem in verification
252      */
253     private WSHandlerResult verify(Document doc) throws Exception {
254         return verify(doc, true);
255     }
256 
257     private WSHandlerResult verify(
258         Document doc,
259         boolean allowUsernameTokenDerivedKeys
260     ) throws Exception {
261         WSSecurityEngine secEngine = new WSSecurityEngine();
262 
263         RequestData requestData = new RequestData();
264         requestData.setAllowUsernameTokenNoPassword(allowUsernameTokenDerivedKeys);
265         requestData.setCallbackHandler(callbackHandler);
266         requestData.setSigVerCrypto(crypto);
267         requestData.setDecCrypto(crypto);
268 
269         return secEngine.processSecurityHeader(doc, requestData);
270     }
271 
272 }