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.io.IOException;
23  import java.util.Collections;
24  
25  import javax.crypto.KeyGenerator;
26  import javax.crypto.SecretKey;
27  import javax.security.auth.callback.Callback;
28  import javax.security.auth.callback.CallbackHandler;
29  import javax.security.auth.callback.UnsupportedCallbackException;
30  import javax.xml.crypto.dsig.SignatureMethod;
31  
32  import org.apache.wss4j.common.util.SOAPUtil;
33  import org.apache.wss4j.dom.WSConstants;
34  import org.apache.wss4j.dom.common.CustomHandler;
35  import org.apache.wss4j.dom.common.KeystoreCallbackHandler;
36  import org.apache.wss4j.dom.common.SecretKeyCallbackHandler;
37  
38  import org.apache.wss4j.dom.engine.WSSConfig;
39  import org.apache.wss4j.dom.engine.WSSecurityEngine;
40  import org.apache.wss4j.dom.engine.WSSecurityEngineResult;
41  import org.apache.wss4j.common.crypto.Crypto;
42  import org.apache.wss4j.common.crypto.CryptoFactory;
43  import org.apache.wss4j.common.ext.WSPasswordCallback;
44  import org.apache.wss4j.common.util.KeyUtils;
45  import org.apache.wss4j.common.util.XMLUtils;
46  import org.apache.wss4j.dom.handler.HandlerAction;
47  import org.apache.wss4j.dom.handler.RequestData;
48  import org.apache.wss4j.dom.handler.WSHandlerConstants;
49  import org.apache.wss4j.dom.handler.WSHandlerResult;
50  
51  import org.junit.jupiter.api.BeforeEach;
52  import org.junit.jupiter.api.Test;
53  import org.w3c.dom.Document;
54  
55  import static org.junit.jupiter.api.Assertions.assertFalse;
56  import static org.junit.jupiter.api.Assertions.assertNotNull;
57  import static org.junit.jupiter.api.Assertions.assertNull;
58  
59  
60  /**
61   * Test symmetric key signature created using an encrypted key
62   * Demonstrates that Signature Crypto object can have null values when
63   * calling processSecurityHeader method of WSSecurityEngine.
64   */
65  public class SymmetricSignatureTest implements CallbackHandler {
66      private static final org.slf4j.Logger LOG =
67          org.slf4j.LoggerFactory.getLogger(SymmetricSignatureTest.class);
68      private WSSecurityEngine secEngine = new WSSecurityEngine();
69      private CallbackHandler callbackHandler = new KeystoreCallbackHandler();
70      private SecretKeyCallbackHandler secretKeyCallbackHandler = new SecretKeyCallbackHandler();
71      private byte[] keyData;
72      private Crypto crypto;
73  
74      public SymmetricSignatureTest() throws Exception {
75          WSSConfig.init();
76          crypto = CryptoFactory.getInstance("wss40.properties");
77      }
78  
79      /**
80       * Setup method
81       * <p/>
82       *
83       * @throws Exception Thrown when there is a problem in setup
84       */
85      @BeforeEach
86      public void setUp() throws Exception {
87          KeyGenerator keyGen = KeyGenerator.getInstance("AES");
88          keyGen.init(128);
89          SecretKey key = keyGen.generateKey();
90          keyData = key.getEncoded();
91      }
92  
93      /**
94       * Test signing a message body using a symmetric key with EncryptedKeySHA1
95       */
96      @Test
97      public void testSymmetricSignatureSHA1() throws Exception {
98          Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
99  
100         WSSecHeader secHeader = new WSSecHeader(doc);
101         secHeader.insertSecurityHeader();
102 
103         WSSecSignature sign = new WSSecSignature(secHeader);
104         sign.setKeyIdentifierType(WSConstants.ENCRYPTED_KEY_SHA1_IDENTIFIER);
105         sign.setSecretKey(keyData);
106         sign.setSignatureAlgorithm(SignatureMethod.HMAC_SHA1);
107 
108         Document signedDoc = sign.build(crypto);
109 
110         byte[] encodedBytes = KeyUtils.generateDigest(keyData);
111         String identifier = org.apache.xml.security.utils.XMLUtils.encodeToString(encodedBytes);
112         secretKeyCallbackHandler.addSecretKey(identifier, keyData);
113 
114         if (LOG.isDebugEnabled()) {
115             LOG.debug("Signed symmetric message SHA1:");
116             String outputString =
117                 XMLUtils.prettyDocumentToString(signedDoc);
118             LOG.debug(outputString);
119         }
120 
121         secEngine.processSecurityHeader(doc, null, secretKeyCallbackHandler, null, crypto);
122     }
123 
124 
125     /**
126      * Test signing a message body using a symmetric key with Direct Reference to an
127      * EncryptedKey
128      */
129     @Test
130     public void testSymmetricSignatureDR() throws Exception {
131         Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
132 
133         WSSecHeader secHeader = new WSSecHeader(doc);
134         secHeader.insertSecurityHeader();
135 
136         WSSecEncryptedKey encrKey = new WSSecEncryptedKey(secHeader);
137         encrKey.setKeyIdentifierType(WSConstants.ISSUER_SERIAL);
138         encrKey.setUserInfo("wss40", "security");
139 
140         KeyGenerator keyGen = KeyUtils.getKeyGenerator(WSConstants.AES_192);
141         SecretKey symmetricKey = keyGen.generateKey();
142         encrKey.prepare(crypto, symmetricKey);
143 
144         WSSecSignature sign = new WSSecSignature(secHeader);
145         sign.setKeyIdentifierType(WSConstants.CUSTOM_SYMM_SIGNING);
146         sign.setCustomTokenId(encrKey.getId());
147         sign.setSecretKey(symmetricKey.getEncoded());
148         sign.setSignatureAlgorithm(SignatureMethod.HMAC_SHA1);
149         sign.setCustomTokenValueType(WSConstants.WSS_ENC_KEY_VALUE_TYPE);
150 
151         Document signedDoc = sign.build(crypto);
152         encrKey.prependToHeader();
153 
154         if (LOG.isDebugEnabled()) {
155             LOG.debug("Signed symmetric message DR:");
156             String outputString =
157                 XMLUtils.prettyDocumentToString(signedDoc);
158             LOG.debug(outputString);
159         }
160 
161         verify(signedDoc);
162     }
163 
164     /**
165      * Test that first signs, then encrypts a WS-Security envelope.
166      * <p/>
167      *
168      * @throws Exception Thrown when there is any problem in signing, encryption,
169      *                   decryption, or verification
170      */
171     @Test
172     public void testEncryptedKeySignature() throws Exception {
173         Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
174         LOG.info("Before Sign/Encryption....");
175 
176         WSSecHeader secHeader = new WSSecHeader(doc);
177         secHeader.insertSecurityHeader();
178 
179         WSSecEncryptedKey encrKey = new WSSecEncryptedKey(secHeader);
180         encrKey.setKeyIdentifierType(WSConstants.ISSUER_SERIAL);
181         encrKey.setUserInfo("wss40", "security");
182 
183         KeyGenerator keyGen = KeyUtils.getKeyGenerator(WSConstants.AES_192);
184         SecretKey symmetricKey = keyGen.generateKey();
185         encrKey.prepare(crypto, symmetricKey);
186 
187         WSSecEncrypt encrypt = new WSSecEncrypt(secHeader);
188         encrypt.setEncKeyId(encrKey.getId());
189         encrypt.setSymmetricEncAlgorithm(WSConstants.TRIPLE_DES);
190         encrypt.setEncryptSymmKey(false);
191         encrypt.setEncryptedKeyElement(encrKey.getEncryptedKeyElement());
192 
193         WSSecSignature sign = new WSSecSignature(secHeader);
194         sign.setKeyIdentifierType(WSConstants.CUSTOM_SYMM_SIGNING);
195         sign.setCustomTokenId(encrKey.getId());
196         sign.setCustomTokenValueType(WSConstants.WSS_ENC_KEY_VALUE_TYPE);
197         sign.setSecretKey(symmetricKey.getEncoded());
198         sign.setSignatureAlgorithm(SignatureMethod.HMAC_SHA1);
199 
200         sign.build(crypto);
201         Document encryptedSignedDoc = encrypt.build(crypto, symmetricKey);
202 
203         if (LOG.isDebugEnabled()) {
204             LOG.debug("Signed and encrypted message with IssuerSerial key identifier (both), 3DES:");
205             String outputString =
206                 XMLUtils.prettyDocumentToString(encryptedSignedDoc);
207             LOG.debug(outputString);
208         }
209 
210         LOG.info("After Sign/Encryption....");
211         verify(encryptedSignedDoc);
212     }
213 
214     /**
215      * Test signing a message body using a symmetric key with EncryptedKeySHA1.
216      * The request is generated using WSHandler, instead of coding it.
217      */
218     @Test
219     public void testSymmetricSignatureSHA1Handler() throws Exception {
220         final WSSConfig cfg = WSSConfig.getNewInstance();
221         RequestData reqData = new RequestData();
222         reqData.setWssConfig(cfg);
223         java.util.Map<String, Object> messageContext = new java.util.TreeMap<>();
224         messageContext.put(WSHandlerConstants.SIG_KEY_ID, "EncryptedKeySHA1");
225         messageContext.put(WSHandlerConstants.SIG_ALGO, SignatureMethod.HMAC_SHA1);
226         messageContext.put(WSHandlerConstants.PW_CALLBACK_REF, this);
227         reqData.setMsgContext(messageContext);
228         reqData.setUsername("");
229 
230         final Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
231         CustomHandler handler = new CustomHandler();
232         HandlerAction action = new HandlerAction(WSConstants.SIGN);
233         handler.send(
234             doc,
235             reqData,
236             Collections.singletonList(action),
237             true
238         );
239 
240         String outputString =
241             XMLUtils.prettyDocumentToString(doc);
242         if (LOG.isDebugEnabled()) {
243             LOG.debug(outputString);
244         }
245 
246         reqData = new RequestData();
247         reqData.setWssConfig(WSSConfig.getNewInstance());
248         messageContext = new java.util.TreeMap<>();
249         messageContext.put(WSHandlerConstants.PW_CALLBACK_REF, this);
250         reqData.setMsgContext(messageContext);
251         reqData.setUsername("");
252 
253         handler.receive(Collections.singletonList(WSConstants.SIGN), reqData);
254 
255         secEngine.processSecurityHeader(doc, null, this, null, crypto);
256     }
257 
258 
259     /**
260      * A signature verified with a symmetric key that arrived in an EncryptedKey establishes
261      * the integrity of the signed parts, but it does not authenticate the sender: anybody
262      * holding this service's certificate can wrap a key of their choosing and sign with it.
263      * No trust decision is taken on such a credential - a symmetric key carries no identity
264      * for a Validator to check - so the result must not advertise itself as a validated
265      * token, even though a Validator is registered for the Signature action by default.
266      */
267     @Test
268     public void testSymmetricSignatureIsNotReportedAsValidated() throws Exception {
269         Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
270 
271         WSSecHeader secHeader = new WSSecHeader(doc);
272         secHeader.insertSecurityHeader();
273 
274         WSSecEncryptedKey encrKey = new WSSecEncryptedKey(secHeader);
275         encrKey.setKeyIdentifierType(WSConstants.ISSUER_SERIAL);
276         encrKey.setUserInfo("wss40", "security");
277 
278         KeyGenerator keyGen = KeyUtils.getKeyGenerator(WSConstants.AES_192);
279         SecretKey symmetricKey = keyGen.generateKey();
280         encrKey.prepare(crypto, symmetricKey);
281 
282         WSSecSignature sign = new WSSecSignature(secHeader);
283         sign.setKeyIdentifierType(WSConstants.CUSTOM_SYMM_SIGNING);
284         sign.setCustomTokenId(encrKey.getId());
285         sign.setSecretKey(symmetricKey.getEncoded());
286         sign.setSignatureAlgorithm(SignatureMethod.HMAC_SHA1);
287         sign.setCustomTokenValueType(WSConstants.WSS_ENC_KEY_VALUE_TYPE);
288 
289         Document signedDoc = sign.build(crypto);
290         encrKey.prependToHeader();
291 
292         WSHandlerResult results = verify(signedDoc);
293 
294         WSSecurityEngineResult actionResult =
295             results.getActionResults().get(WSConstants.SIGN).get(0);
296         assertNotNull(actionResult);
297 
298         // The signature was keyed by the symmetric key, not by a certificate or public key
299         assertNotNull(actionResult.get(WSSecurityEngineResult.TAG_SECRET));
300         assertNull(actionResult.get(WSSecurityEngineResult.TAG_X509_CERTIFICATES));
301         assertNull(actionResult.get(WSSecurityEngineResult.TAG_PUBLIC_KEY));
302 
303         assertFalse((Boolean)actionResult.get(WSSecurityEngineResult.TAG_VALIDATED_TOKEN),
304             "A signature keyed by an EncryptedKey secret must not be reported as a "
305             + "validated token: no trust decision was taken on the key");
306     }
307 
308     /**
309      * Verifies the soap envelope
310      * <p/>
311      *
312      * @param doc
313      * @throws Exception Thrown when there is a problem in verification
314      */
315     private WSHandlerResult verify(Document doc) throws Exception {
316         WSHandlerResult results =
317             secEngine.processSecurityHeader(doc, null, callbackHandler, null, crypto);
318         if (LOG.isDebugEnabled()) {
319             LOG.debug("Verfied and decrypted message:");
320             String outputString =
321                 XMLUtils.prettyDocumentToString(doc);
322             LOG.debug(outputString);
323         }
324         return results;
325     }
326 
327     public void handle(Callback[] callbacks)
328         throws IOException, UnsupportedCallbackException {
329         for (Callback callback : callbacks) {
330             if (callback instanceof WSPasswordCallback) {
331                 WSPasswordCallback pc = (WSPasswordCallback) callback;
332                 /*
333                  * here call a function/method to lookup the password for
334                  * the given identifier (e.g. a user name or keystore alias)
335                  * e.g.: pc.setPassword(passStore.getPassword(pc.getIdentfifier))
336                  * for Testing we supply a fixed name here.
337                  */
338                 pc.setPassword("security");
339                 pc.setKey(keyData);
340             } else {
341                 throw new UnsupportedCallbackException(callback, "Unrecognized Callback");
342             }
343         }
344     }
345 
346 
347 }