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 java.nio.file.Path;
23  import java.util.List;
24  
25  import javax.security.auth.callback.CallbackHandler;
26  
27  import org.apache.wss4j.common.cache.EHCacheReplayCache;
28  import org.apache.wss4j.common.util.SOAPUtil;
29  import org.apache.wss4j.dom.WSConstants;
30  import org.apache.wss4j.dom.common.KeystoreCallbackHandler;
31  import org.apache.wss4j.dom.common.SAML2CallbackHandler;
32  
33  import org.apache.wss4j.dom.common.UsernamePasswordCallbackHandler;
34  import org.apache.wss4j.dom.engine.WSSConfig;
35  import org.apache.wss4j.dom.engine.WSSecurityEngine;
36  import org.apache.wss4j.common.WSEncryptionPart;
37  import org.apache.wss4j.common.cache.MemoryReplayCache;
38  import org.apache.wss4j.common.cache.ReplayCache;
39  import org.apache.wss4j.common.crypto.Crypto;
40  import org.apache.wss4j.common.crypto.CryptoFactory;
41  import org.apache.wss4j.common.ext.WSSecurityException;
42  import org.apache.wss4j.common.saml.SAMLCallback;
43  import org.apache.wss4j.common.saml.SAMLUtil;
44  import org.apache.wss4j.common.saml.SamlAssertionWrapper;
45  import org.apache.wss4j.common.saml.bean.ConditionsBean;
46  import org.apache.wss4j.common.saml.builder.SAML2Constants;
47  import org.apache.wss4j.common.util.XMLUtils;
48  import org.apache.wss4j.dom.handler.RequestData;
49  import org.apache.wss4j.dom.handler.WSHandlerResult;
50  import org.apache.wss4j.dom.util.WSSecurityUtil;
51  import org.apache.wss4j.dom.validate.SamlAssertionValidator;
52  
53  import org.junit.jupiter.api.Test;
54  import org.junit.jupiter.api.io.TempDir;
55  import org.w3c.dom.Document;
56  import org.w3c.dom.Element;
57  
58  import static org.junit.jupiter.api.Assertions.assertTrue;
59  import static org.junit.jupiter.api.Assertions.fail;
60  
61  /**
62   * Some test-cases for replay attacks.
63   */
64  public class ReplayTest {
65      private static final org.slf4j.Logger LOG =
66          org.slf4j.LoggerFactory.getLogger(ReplayTest.class);
67  
68      private CallbackHandler callbackHandler = new KeystoreCallbackHandler();
69      private Crypto crypto;
70  
71      @TempDir
72      Path tempDir;
73  
74      public ReplayTest() throws Exception {
75          crypto = CryptoFactory.getInstance();
76      }
77  
78      private ReplayCache createCache(String key) throws WSSecurityException {
79          return new EHCacheReplayCache(key, tempDir);
80      }
81  
82      @Test
83      public void testReplayedTimestamp() throws Exception {
84  
85          Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
86          WSSecHeader secHeader = new WSSecHeader(doc);
87          secHeader.insertSecurityHeader();
88  
89          WSSecTimestamp timestamp = new WSSecTimestamp(secHeader);
90          timestamp.setTimeToLive(300);
91          Document createdDoc = timestamp.build();
92  
93          WSSecSignature builder = new WSSecSignature(secHeader);
94          builder.setUserInfo("16c73ab6-b892-458f-abf5-2f875f74882e", "security");
95          builder.setKeyIdentifierType(WSConstants.ISSUER_SERIAL);
96  
97          WSEncryptionPart encP =
98              new WSEncryptionPart(
99                  "Timestamp", WSConstants.WSU_NS, "");
100         builder.getParts().add(encP);
101 
102         builder.prepare(crypto);
103 
104         List<javax.xml.crypto.dsig.Reference> referenceList =
105             builder.addReferencesToSign(builder.getParts());
106 
107         builder.computeSignature(referenceList, false, null);
108 
109         if (LOG.isDebugEnabled()) {
110             String outputString =
111                 XMLUtils.prettyDocumentToString(createdDoc);
112             LOG.debug(outputString);
113         }
114 
115         WSSConfig wssConfig = WSSConfig.getNewInstance();
116         RequestData data = new RequestData();
117         data.setWssConfig(wssConfig);
118         data.setCallbackHandler(callbackHandler);
119         data.setTimestampReplayCache(new MemoryReplayCache());
120 
121         // Successfully verify timestamp
122         verify(createdDoc, wssConfig, data);
123 
124         // Now try again - a replay attack should be detected
125         try {
126             verify(createdDoc, wssConfig, data);
127             fail("Expected failure on a replay attack");
128         } catch (WSSecurityException ex) {
129             assertTrue(ex.getErrorCode() == WSSecurityException.ErrorCode.INVALID_SECURITY);
130         }
131     }
132 
133     @Test
134     public void testEhCacheReplayedTimestamp() throws Exception {
135 
136         Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
137         WSSecHeader secHeader = new WSSecHeader(doc);
138         secHeader.insertSecurityHeader();
139 
140         WSSecTimestamp timestamp = new WSSecTimestamp(secHeader);
141         timestamp.setTimeToLive(300);
142         Document createdDoc = timestamp.build();
143 
144         WSSecSignature builder = new WSSecSignature(secHeader);
145         builder.setUserInfo("16c73ab6-b892-458f-abf5-2f875f74882e", "security");
146         builder.setKeyIdentifierType(WSConstants.ISSUER_SERIAL);
147 
148         WSEncryptionPart encP =
149             new WSEncryptionPart(
150                 "Timestamp", WSConstants.WSU_NS, "");
151         builder.getParts().add(encP);
152 
153         builder.prepare(crypto);
154 
155         List<javax.xml.crypto.dsig.Reference> referenceList =
156             builder.addReferencesToSign(builder.getParts());
157 
158         builder.computeSignature(referenceList, false, null);
159 
160         if (LOG.isDebugEnabled()) {
161             String outputString =
162                 XMLUtils.prettyDocumentToString(createdDoc);
163             LOG.debug(outputString);
164         }
165 
166         WSSConfig wssConfig = WSSConfig.getNewInstance();
167         RequestData data = new RequestData();
168         data.setWssConfig(wssConfig);
169         data.setCallbackHandler(callbackHandler);
170         ReplayCache replayCache = createCache("wss4j.timestamp.cache-");
171         data.setTimestampReplayCache(replayCache);
172 
173         // Successfully verify timestamp
174         verify(createdDoc, wssConfig, data);
175 
176         // Now try again - a replay attack should be detected
177         try {
178             verify(createdDoc, wssConfig, data);
179             fail("Expected failure on a replay attack");
180         } catch (WSSecurityException ex) {
181             assertTrue(ex.getErrorCode() == WSSecurityException.ErrorCode.INVALID_SECURITY);
182         }
183 
184         replayCache.close();
185     }
186 
187     @Test
188     public void testReplayedTimestampBelowSignature() throws Exception {
189 
190         Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
191         WSSecHeader secHeader = new WSSecHeader(doc);
192         secHeader.insertSecurityHeader();
193 
194         WSSecTimestamp timestamp = new WSSecTimestamp(secHeader);
195         timestamp.setTimeToLive(300);
196         Document createdDoc = timestamp.build();
197 
198         WSSecSignature builder = new WSSecSignature(secHeader);
199         builder.setUserInfo("16c73ab6-b892-458f-abf5-2f875f74882e", "security");
200         builder.setKeyIdentifierType(WSConstants.ISSUER_SERIAL);
201 
202         WSEncryptionPart encP =
203             new WSEncryptionPart(
204                 "Timestamp", WSConstants.WSU_NS, "");
205         builder.getParts().add(encP);
206 
207         builder.build(crypto);
208 
209         if (LOG.isDebugEnabled()) {
210             String outputString =
211                 XMLUtils.prettyDocumentToString(createdDoc);
212             LOG.debug(outputString);
213         }
214 
215         WSSConfig wssConfig = WSSConfig.getNewInstance();
216         RequestData data = new RequestData();
217         data.setWssConfig(wssConfig);
218         data.setCallbackHandler(callbackHandler);
219         data.setTimestampReplayCache(new MemoryReplayCache());
220 
221         // Successfully verify timestamp
222         verify(createdDoc, wssConfig, data);
223 
224         // Now try again - a replay attack should be detected
225         try {
226             verify(createdDoc, wssConfig, data);
227             fail("Expected failure on a replay attack");
228         } catch (WSSecurityException ex) {
229             assertTrue(ex.getErrorCode() == WSSecurityException.ErrorCode.INVALID_SECURITY);
230         }
231     }
232 
233     @Test
234     public void testEhCacheReplayedTimestampBelowSignature() throws Exception {
235 
236         Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
237         WSSecHeader secHeader = new WSSecHeader(doc);
238         secHeader.insertSecurityHeader();
239 
240         WSSecTimestamp timestamp = new WSSecTimestamp(secHeader);
241         timestamp.setTimeToLive(300);
242         Document createdDoc = timestamp.build();
243 
244         WSSecSignature builder = new WSSecSignature(secHeader);
245         builder.setUserInfo("16c73ab6-b892-458f-abf5-2f875f74882e", "security");
246         builder.setKeyIdentifierType(WSConstants.ISSUER_SERIAL);
247 
248         WSEncryptionPart encP =
249             new WSEncryptionPart(
250                 "Timestamp", WSConstants.WSU_NS, "");
251         builder.getParts().add(encP);
252 
253         builder.build(crypto);
254 
255         if (LOG.isDebugEnabled()) {
256             String outputString =
257                 XMLUtils.prettyDocumentToString(createdDoc);
258             LOG.debug(outputString);
259         }
260 
261         WSSConfig wssConfig = WSSConfig.getNewInstance();
262         RequestData data = new RequestData();
263         data.setWssConfig(wssConfig);
264         data.setCallbackHandler(callbackHandler);
265         ReplayCache replayCache = createCache("wss4j.timestamp.cache-");
266         data.setTimestampReplayCache(replayCache);
267 
268         // Successfully verify timestamp
269         verify(createdDoc, wssConfig, data);
270 
271         // Now try again - a replay attack should be detected
272         try {
273             verify(createdDoc, wssConfig, data);
274             fail("Expected failure on a replay attack");
275         } catch (WSSecurityException ex) {
276             assertTrue(ex.getErrorCode() == WSSecurityException.ErrorCode.INVALID_SECURITY);
277         }
278 
279         replayCache.close();
280     }
281 
282     @Test
283     public void testReplayedTimestampNoExpires() throws Exception {
284 
285         Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
286         WSSecHeader secHeader = new WSSecHeader(doc);
287         secHeader.insertSecurityHeader();
288 
289         WSSecTimestamp timestamp = new WSSecTimestamp(secHeader);
290         timestamp.setTimeToLive(0);
291         Document createdDoc = timestamp.build();
292 
293         WSSecSignature builder = new WSSecSignature(secHeader);
294         builder.setUserInfo("16c73ab6-b892-458f-abf5-2f875f74882e", "security");
295         builder.setKeyIdentifierType(WSConstants.ISSUER_SERIAL);
296 
297         WSEncryptionPart encP =
298             new WSEncryptionPart(
299                 "Timestamp", WSConstants.WSU_NS, "");
300         builder.getParts().add(encP);
301 
302         builder.prepare(crypto);
303 
304         List<javax.xml.crypto.dsig.Reference> referenceList =
305             builder.addReferencesToSign(builder.getParts());
306 
307         builder.computeSignature(referenceList, false, null);
308 
309         if (LOG.isDebugEnabled()) {
310             String outputString =
311                 XMLUtils.prettyDocumentToString(createdDoc);
312             LOG.debug(outputString);
313         }
314 
315         WSSConfig wssConfig = WSSConfig.getNewInstance();
316         RequestData data = new RequestData();
317         data.setWssConfig(wssConfig);
318         data.setCallbackHandler(callbackHandler);
319         data.setTimestampReplayCache(new MemoryReplayCache());
320 
321         // Successfully verify timestamp
322         verify(createdDoc, wssConfig, data);
323 
324         // Now try again - a replay attack should be detected
325         try {
326             verify(createdDoc, wssConfig, data);
327             fail("Expected failure on a replay attack");
328         } catch (WSSecurityException ex) {
329             assertTrue(ex.getErrorCode() == WSSecurityException.ErrorCode.INVALID_SECURITY);
330         }
331     }
332 
333     @Test
334     public void testEhCacheReplayedTimestampNoExpires() throws Exception {
335 
336         Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
337         WSSecHeader secHeader = new WSSecHeader(doc);
338         secHeader.insertSecurityHeader();
339 
340         WSSecTimestamp timestamp = new WSSecTimestamp(secHeader);
341         timestamp.setTimeToLive(0);
342         Document createdDoc = timestamp.build();
343 
344         WSSecSignature builder = new WSSecSignature(secHeader);
345         builder.setUserInfo("16c73ab6-b892-458f-abf5-2f875f74882e", "security");
346         builder.setKeyIdentifierType(WSConstants.ISSUER_SERIAL);
347 
348         WSEncryptionPart encP =
349             new WSEncryptionPart(
350                 "Timestamp", WSConstants.WSU_NS, "");
351         builder.getParts().add(encP);
352 
353         builder.prepare(crypto);
354 
355         List<javax.xml.crypto.dsig.Reference> referenceList =
356             builder.addReferencesToSign(builder.getParts());
357 
358         builder.computeSignature(referenceList, false, null);
359 
360         if (LOG.isDebugEnabled()) {
361             String outputString =
362                 XMLUtils.prettyDocumentToString(createdDoc);
363             LOG.debug(outputString);
364         }
365 
366         WSSConfig wssConfig = WSSConfig.getNewInstance();
367         RequestData data = new RequestData();
368         data.setWssConfig(wssConfig);
369         data.setCallbackHandler(callbackHandler);
370         ReplayCache replayCache = createCache("wss4j.timestamp.cache-");
371         data.setTimestampReplayCache(replayCache);
372 
373         // Successfully verify timestamp
374         verify(createdDoc, wssConfig, data);
375 
376         // Now try again - a replay attack should be detected
377         try {
378             verify(createdDoc, wssConfig, data);
379             fail("Expected failure on a replay attack");
380         } catch (WSSecurityException ex) {
381             assertTrue(ex.getErrorCode() == WSSecurityException.ErrorCode.INVALID_SECURITY);
382         }
383 
384         replayCache.close();
385     }
386 
387     @Test
388     public void testReplayedUsernameToken() throws Exception {
389         Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
390         WSSecHeader secHeader = new WSSecHeader(doc);
391         secHeader.insertSecurityHeader();
392 
393         WSSecUsernameToken builder = new WSSecUsernameToken(secHeader);
394         builder.setUserInfo("wernerd", "verySecret");
395 
396         Document signedDoc = builder.build();
397 
398         if (LOG.isDebugEnabled()) {
399             String outputString =
400                 XMLUtils.prettyDocumentToString(signedDoc);
401             LOG.debug(outputString);
402         }
403 
404         WSSConfig wssConfig = WSSConfig.getNewInstance();
405         RequestData data = new RequestData();
406         data.setCallbackHandler(new UsernamePasswordCallbackHandler());
407         data.setWssConfig(wssConfig);
408         data.setNonceReplayCache(new MemoryReplayCache());
409 
410         // Successfully verify UsernameToken
411         verify(signedDoc, wssConfig, data);
412 
413         // Now try again - a replay attack should be detected
414         try {
415             verify(signedDoc, wssConfig, data);
416             fail("Expected failure on a replay attack");
417         } catch (WSSecurityException ex) {
418             assertTrue(ex.getErrorCode() == WSSecurityException.ErrorCode.INVALID_SECURITY);
419         }
420     }
421 
422     @Test
423     public void testEhCacheReplayedUsernameToken() throws Exception {
424         Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
425         WSSecHeader secHeader = new WSSecHeader(doc);
426         secHeader.insertSecurityHeader();
427 
428         WSSecUsernameToken builder = new WSSecUsernameToken(secHeader);
429         builder.setUserInfo("wernerd", "verySecret");
430 
431         Document signedDoc = builder.build();
432 
433         if (LOG.isDebugEnabled()) {
434             String outputString =
435                 XMLUtils.prettyDocumentToString(signedDoc);
436             LOG.debug(outputString);
437         }
438 
439         WSSConfig wssConfig = WSSConfig.getNewInstance();
440         RequestData data = new RequestData();
441         data.setCallbackHandler(new UsernamePasswordCallbackHandler());
442         data.setWssConfig(wssConfig);
443         ReplayCache replayCache = createCache("wss4j.nonce.cache-");
444         data.setNonceReplayCache(replayCache);
445 
446         // Successfully verify UsernameToken
447         verify(signedDoc, wssConfig, data);
448 
449         // Now try again - a replay attack should be detected
450         try {
451             verify(signedDoc, wssConfig, data);
452             fail("Expected failure on a replay attack");
453         } catch (WSSecurityException ex) {
454             assertTrue(ex.getErrorCode() == WSSecurityException.ErrorCode.INVALID_SECURITY);
455         }
456 
457         replayCache.close();
458     }
459 
460     /**
461      * Test that creates, sends and processes an unsigned SAML 2 authentication assertion. This
462      * is just a sanity test to make sure that it is possible to send the SAML token twice, as
463      * no "OneTimeUse" Element is defined there is no problem with replaying it.
464      * with a OneTimeUse Element
465      */
466     @Test
467     public void testEhCacheReplayedSAML2() throws Exception {
468         SAML2CallbackHandler callbackHandler = new SAML2CallbackHandler();
469         callbackHandler.setStatement(SAML2CallbackHandler.Statement.AUTHN);
470         callbackHandler.setIssuer("www.example.com");
471         callbackHandler.setConfirmationMethod(SAML2Constants.CONF_BEARER);
472 
473         ConditionsBean conditions = new ConditionsBean();
474         conditions.setTokenPeriodMinutes(5);
475 
476         callbackHandler.setConditions(conditions);
477 
478         SAMLCallback samlCallback = new SAMLCallback();
479         SAMLUtil.doSAMLCallback(callbackHandler, samlCallback);
480         SamlAssertionWrapper samlAssertion = new SamlAssertionWrapper(samlCallback);
481 
482         Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
483         WSSecHeader secHeader = new WSSecHeader(doc);
484         secHeader.insertSecurityHeader();
485 
486         WSSecSAMLToken wsSign = new WSSecSAMLToken(secHeader);
487 
488         Document unsignedDoc = wsSign.build(samlAssertion);
489 
490         if (LOG.isDebugEnabled()) {
491             String outputString = XMLUtils.prettyDocumentToString(unsignedDoc);
492             LOG.debug(outputString);
493         }
494 
495         WSSConfig wssConfig = WSSConfig.getNewInstance();
496         SamlAssertionValidator assertionValidator = new SamlAssertionValidator();
497         assertionValidator.setRequireBearerSignature(false);
498         wssConfig.setValidator(WSConstants.SAML_TOKEN, assertionValidator);
499         wssConfig.setValidator(WSConstants.SAML2_TOKEN, assertionValidator);
500 
501         RequestData data = new RequestData();
502         data.setWssConfig(wssConfig);
503         data.setCallbackHandler(callbackHandler);
504         ReplayCache replayCache = createCache("wss4j.saml.one.time.use.cache-");
505         data.setSamlOneTimeUseReplayCache(replayCache);
506 
507         // Successfully verify SAML Token
508         verify(unsignedDoc, wssConfig, data);
509 
510         // Now try again - this should work fine as well
511         verify(unsignedDoc, wssConfig, data);
512 
513         replayCache.close();
514     }
515 
516     /**
517      * Test that creates, sends and processes an unsigned SAML 2 authentication assertion
518      * with a OneTimeUse Element
519      */
520     @Test
521     public void testEhCacheReplayedSAML2OneTimeUse() throws Exception {
522         SAML2CallbackHandler callbackHandler = new SAML2CallbackHandler();
523         callbackHandler.setStatement(SAML2CallbackHandler.Statement.AUTHN);
524         callbackHandler.setIssuer("www.example.com");
525         callbackHandler.setConfirmationMethod(SAML2Constants.CONF_BEARER);
526 
527         ConditionsBean conditions = new ConditionsBean();
528         conditions.setTokenPeriodMinutes(5);
529         conditions.setOneTimeUse(true);
530 
531         callbackHandler.setConditions(conditions);
532 
533         SAMLCallback samlCallback = new SAMLCallback();
534         SAMLUtil.doSAMLCallback(callbackHandler, samlCallback);
535         SamlAssertionWrapper samlAssertion = new SamlAssertionWrapper(samlCallback);
536 
537         Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
538         WSSecHeader secHeader = new WSSecHeader(doc);
539         secHeader.insertSecurityHeader();
540 
541         WSSecSAMLToken wsSign = new WSSecSAMLToken(secHeader);
542 
543         Document unsignedDoc = wsSign.build(samlAssertion);
544 
545         String outputString =
546             XMLUtils.prettyDocumentToString(unsignedDoc);
547         assertTrue(outputString.contains("OneTimeUse"));
548         if (LOG.isDebugEnabled()) {
549             LOG.debug(outputString);
550         }
551 
552         WSSConfig wssConfig = WSSConfig.getNewInstance();
553         SamlAssertionValidator assertionValidator = new SamlAssertionValidator();
554         assertionValidator.setRequireBearerSignature(false);
555         wssConfig.setValidator(WSConstants.SAML_TOKEN, assertionValidator);
556         wssConfig.setValidator(WSConstants.SAML2_TOKEN, assertionValidator);
557 
558         RequestData data = new RequestData();
559         data.setWssConfig(wssConfig);
560         data.setCallbackHandler(callbackHandler);
561         ReplayCache replayCache = createCache("wss4j.saml.one.time.use.cache-");
562         data.setSamlOneTimeUseReplayCache(replayCache);
563 
564         // Successfully verify SAML Token
565         verify(unsignedDoc, wssConfig, data);
566 
567         // Now try again - a replay attack should be detected
568         try {
569             verify(unsignedDoc, wssConfig, data);
570             fail("Expected failure on a replay attack");
571         } catch (WSSecurityException ex) {
572             assertTrue(ex.getErrorCode() == WSSecurityException.ErrorCode.INVALID_SECURITY);
573         }
574 
575         replayCache.close();
576     }
577 
578     /**
579      * Verifies the soap envelope
580      *
581      * @param doc soap document
582      * @param wssConfig
583      * @throws Exception Thrown when there is a problem in verification
584      */
585     private WSHandlerResult verify(
586         Document doc, WSSConfig wssConfig, RequestData data
587     ) throws Exception {
588         WSSecurityEngine secEngine = new WSSecurityEngine();
589         secEngine.setWssConfig(wssConfig);
590         Element elem = WSSecurityUtil.getSecurityHeader(doc, null);
591         data.setSigVerCrypto(crypto);
592         return secEngine.processSecurityHeader(elem, data);
593     }
594 
595 
596 }