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.message;
21  
22  import org.apache.ws.security.SOAPConstants;
23  import org.apache.ws.security.WSDataRef;
24  import org.apache.ws.security.WSEncryptionPart;
25  import org.apache.ws.security.WSSConfig;
26  import org.apache.ws.security.WSSecurityEngine;
27  import org.apache.ws.security.WSConstants;
28  import org.apache.ws.security.WSSecurityEngineResult;
29  import org.apache.ws.security.WSSecurityException;
30  import org.apache.ws.security.common.SAML1CallbackHandler;
31  import org.apache.ws.security.common.SOAPUtil;
32  import org.apache.ws.security.components.crypto.Crypto;
33  import org.apache.ws.security.components.crypto.CryptoFactory;
34  import org.apache.ws.security.components.crypto.Merlin;
35  import org.apache.ws.security.saml.SAMLIssuer;
36  import org.apache.ws.security.saml.SAMLIssuerImpl;
37  import org.apache.ws.security.saml.SignedSamlTokenHOKTest;
38  import org.apache.ws.security.saml.WSSecSignatureSAML;
39  import org.apache.ws.security.saml.ext.AssertionWrapper;
40  import org.apache.ws.security.saml.ext.builder.SAML1Constants;
41  import org.apache.ws.security.util.Loader;
42  import org.apache.ws.security.util.WSSecurityUtil;
43  import org.w3c.dom.Document;
44  import org.w3c.dom.Element;
45  
46  import java.io.InputStream;
47  import java.security.KeyStore;
48  import java.util.List;
49  import java.util.ArrayList;
50  import javax.xml.namespace.QName;
51  
52  /**
53   * This is some unit tests for signing using signature parts. Note that the "soapMsg" below
54   * has a custom header added.
55   */
56  public class SignaturePartsTest extends org.junit.Assert {
57      private static final org.apache.commons.logging.Log LOG = 
58          org.apache.commons.logging.LogFactory.getLog(SignaturePartsTest.class);
59      private static final String SOAPMSG = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
60              "<soapenv:Envelope xmlns:foo=\"urn:foo.bar\" xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" +
61              "   <soapenv:Header>" +
62              "       <foo:foobar>baz</foo:foobar>" + 
63              "   </soapenv:Header>" +
64              "   <soapenv:Body>" +
65              "      <ns1:testMethod xmlns:ns1=\"http://axis/service/security/test6/LogTestService8\"></ns1:testMethod>" +
66              "   </soapenv:Body>" +
67              "</soapenv:Envelope>";
68      private static final String SOAPMSG_MULTIPLE = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
69          "<soapenv:Envelope xmlns:foo=\"urn:foo.bar\" xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" +
70          "   <soapenv:Header>" +
71          "       <foo:foobar>baz</foo:foobar>" + 
72          "   </soapenv:Header>" +
73          "   <soapenv:Body>" +
74          "      <ns1:testMethod xmlns:ns1=\"http://axis/service/security/test6/LogTestService8\">asf1</ns1:testMethod>" +
75          "      <ns1:testMethod xmlns:ns1=\"http://axis/service/security/test6/LogTestService8\">asf2</ns1:testMethod>" +
76          "   </soapenv:Body>" +
77          "</soapenv:Envelope>";
78  
79      private WSSecurityEngine secEngine = new WSSecurityEngine();
80      private Crypto crypto = null;
81      
82      public SignaturePartsTest() throws Exception {
83          WSSConfig.init();
84          crypto = CryptoFactory.getInstance();
85      }
86  
87      /**
88       * Test signing a custom SOAP header
89       */
90      @SuppressWarnings("unchecked")
91      @org.junit.Test
92      public void testSOAPHeader() throws Exception {
93          WSSecSignature sign = new WSSecSignature();
94          sign.setUserInfo("16c73ab6-b892-458f-abf5-2f875f74882e", "security");
95          sign.setKeyIdentifierType(WSConstants.ISSUER_SERIAL);
96  
97          Document doc = SOAPUtil.toSOAPPart(SOAPMSG);
98  
99          WSSecHeader secHeader = new WSSecHeader();
100         secHeader.insertSecurityHeader(doc);
101         
102         List<WSEncryptionPart> parts = new ArrayList<WSEncryptionPart>();
103         WSEncryptionPart encP =
104             new WSEncryptionPart(
105                 "foobar",
106                 "urn:foo.bar",
107                 "");
108         parts.add(encP);
109         sign.setParts(parts);
110         
111         Document signedDoc = sign.build(doc, crypto, secHeader);
112         
113         if (LOG.isDebugEnabled()) {
114             String outputString = 
115                 org.apache.ws.security.util.XMLUtils.PrettyDocumentToString(signedDoc);
116             LOG.debug(outputString);
117         }
118         
119         List<WSSecurityEngineResult> results = verify(signedDoc);
120         
121         QName name = new QName("urn:foo.bar", "foobar");
122         WSSecurityUtil.checkAllElementsProtected(results, WSConstants.SIGN, new QName[]{name});
123         try {
124             name = new QName("urn:foo.bar", "foobar2");
125             WSSecurityUtil.checkAllElementsProtected(results, WSConstants.SIGN, new QName[]{name});
126             fail("Failure expected on a wrong protected part");
127         } catch (WSSecurityException ex) {
128             // expected
129         }
130         try {
131             name = new QName("urn:foo.bar", "foobar");
132             WSSecurityUtil.checkAllElementsProtected(results, WSConstants.ENCR, new QName[]{name});
133             fail("Failure expected on a wrong action");
134         } catch (WSSecurityException ex) {
135             // expected
136         }
137         
138         WSSecurityEngineResult actionResult = 
139             WSSecurityUtil.fetchActionResult(results, WSConstants.SIGN);
140         assertTrue(actionResult != null);
141         assertFalse(actionResult.isEmpty());
142         final List<WSDataRef> refs =
143             (List<WSDataRef>) actionResult.get(WSSecurityEngineResult.TAG_DATA_REF_URIS);
144         
145         WSDataRef wsDataRef = (WSDataRef)refs.get(0);
146         String xpath = wsDataRef.getXpath();
147         assertEquals("/soapenv:Envelope/soapenv:Header/foo:foobar", xpath);
148         assertEquals(WSConstants.RSA_SHA1, wsDataRef.getAlgorithm());
149         
150         assertEquals(WSConstants.SHA1, wsDataRef.getDigestAlgorithm());
151         
152         String sigMethod = (String)actionResult.get(WSSecurityEngineResult.TAG_SIGNATURE_METHOD);
153         assertEquals(WSConstants.RSA_SHA1, sigMethod);
154         
155         String c14nMethod = 
156             (String)actionResult.get(WSSecurityEngineResult.TAG_CANONICALIZATION_METHOD);
157         assertEquals(WSConstants.C14N_EXCL_OMIT_COMMENTS, c14nMethod);
158         
159         List<String> transformAlgorithms = wsDataRef.getTransformAlgorithms();
160         assertTrue(transformAlgorithms.size() == 1);
161         assertTrue(WSConstants.C14N_EXCL_OMIT_COMMENTS.equals(transformAlgorithms.get(0)));
162     }
163     
164     /**
165      * Test signing of a header through a STR Dereference Transform
166      */
167     @SuppressWarnings("unchecked")
168     @org.junit.Test
169     public void testSOAPHeaderSTRTransform() throws Exception {
170         // Construct issuer and user crypto instances
171         Crypto issuerCrypto = new Merlin();
172         KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
173         ClassLoader loader = Loader.getClassLoader(SignedSamlTokenHOKTest.class);
174         InputStream input = Merlin.loadInputStream(loader, "keys/wss40_server.jks");
175         keyStore.load(input, "security".toCharArray());
176         ((Merlin)issuerCrypto).setKeyStore(keyStore);
177         
178         Crypto userCrypto = CryptoFactory.getInstance("wss40.properties");
179         
180         SAML1CallbackHandler callbackHandler = new SAML1CallbackHandler();
181         callbackHandler.setStatement(SAML1CallbackHandler.Statement.AUTHN);
182         callbackHandler.setConfirmationMethod(SAML1Constants.CONF_HOLDER_KEY);
183         SAMLIssuer saml = new SAMLIssuerImpl();
184         saml.setIssuerName("www.example.com");
185         saml.setIssuerCrypto(issuerCrypto);
186         saml.setIssuerKeyName("wss40_server");
187         saml.setIssuerKeyPassword("security");
188         saml.setSignAssertion(true);
189         saml.setCallbackHandler(callbackHandler);
190         AssertionWrapper assertion = saml.newAssertion();
191 
192         WSSecSignatureSAML wsSign = new WSSecSignatureSAML();
193         wsSign.setKeyIdentifierType(WSConstants.BST_DIRECT_REFERENCE);
194         wsSign.setUserInfo("wss40", "security");
195         
196         Document doc = SOAPUtil.toSOAPPart(SOAPMSG);
197         WSSecHeader secHeader = new WSSecHeader();
198         secHeader.insertSecurityHeader(doc);
199         
200         List<WSEncryptionPart> parts = new ArrayList<WSEncryptionPart>();
201         WSEncryptionPart encP =
202             new WSEncryptionPart("STRTransform", "", "Element");
203         parts.add(encP);
204         wsSign.setParts(parts);
205 
206         //
207         // set up for keyHolder
208         //
209         Document signedDoc = wsSign.build(doc, userCrypto, assertion, null, null, null, secHeader);
210 
211         if (LOG.isDebugEnabled()) {
212             LOG.debug("Signed SAML message (key holder):");
213             String outputString = 
214                 org.apache.ws.security.util.XMLUtils.PrettyDocumentToString(signedDoc);
215             LOG.debug(outputString);
216         }
217         
218         // Construct trust crypto instance
219         Crypto trustCrypto = new Merlin();
220         KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
221         input = Merlin.loadInputStream(loader, "keys/wss40CA.jks");
222         trustStore.load(input, "security".toCharArray());
223         ((Merlin)trustCrypto).setTrustStore(trustStore);
224         
225         List<WSSecurityEngineResult> results = 
226             secEngine.processSecurityHeader(doc, null, null, trustCrypto);
227         WSSecurityEngineResult stUnsignedActionResult =
228             WSSecurityUtil.fetchActionResult(results, WSConstants.ST_SIGNED);
229         AssertionWrapper receivedAssertion = 
230             (AssertionWrapper) stUnsignedActionResult.get(WSSecurityEngineResult.TAG_SAML_ASSERTION);
231         assertTrue(receivedAssertion != null);
232         assertTrue(receivedAssertion.isSigned());
233         
234         WSSecurityEngineResult signActionResult = 
235             WSSecurityUtil.fetchActionResult(results, WSConstants.SIGN);
236         assertTrue(signActionResult != null);
237         assertFalse(signActionResult.isEmpty());
238         final List<WSDataRef> refs =
239             (List<WSDataRef>) signActionResult.get(WSSecurityEngineResult.TAG_DATA_REF_URIS);
240         
241         WSDataRef wsDataRef = (WSDataRef)refs.get(0);
242         String xpath = wsDataRef.getXpath();
243         assertEquals("/soapenv:Envelope/soapenv:Header/wsse:Security/saml1:Assertion", xpath);
244     }
245     
246     /**
247      * Test signing a custom SOAP header with a bad localname
248      */
249     @org.junit.Test
250     public void testBadLocalname() throws Exception {
251         WSSecSignature sign = new WSSecSignature();
252         sign.setUserInfo("16c73ab6-b892-458f-abf5-2f875f74882e", "security");
253         sign.setKeyIdentifierType(WSConstants.ISSUER_SERIAL);
254 
255         Document doc = SOAPUtil.toSOAPPart(SOAPMSG);
256 
257         WSSecHeader secHeader = new WSSecHeader();
258         secHeader.insertSecurityHeader(doc);
259         
260         List<WSEncryptionPart> parts = new ArrayList<WSEncryptionPart>();
261         WSEncryptionPart encP =
262             new WSEncryptionPart(
263                 "foobar2",
264                 "urn:foo.bar",
265                 "");
266         parts.add(encP);
267         sign.setParts(parts);
268         
269         try {
270             sign.build(doc, crypto, secHeader);
271             fail("Failure expected on a bad localname");
272         } catch (WSSecurityException ex) {
273             // expected
274         }
275     }
276     
277     /**
278      * Test signing a custom SOAP header with a bad namespace
279      */
280     @org.junit.Test
281     public void testBadNamespace() throws Exception {
282         WSSecSignature sign = new WSSecSignature();
283         sign.setUserInfo("16c73ab6-b892-458f-abf5-2f875f74882e", "security");
284         sign.setKeyIdentifierType(WSConstants.ISSUER_SERIAL);
285 
286         Document doc = SOAPUtil.toSOAPPart(SOAPMSG);
287 
288         WSSecHeader secHeader = new WSSecHeader();
289         secHeader.insertSecurityHeader(doc);
290         
291         List<WSEncryptionPart> parts = new ArrayList<WSEncryptionPart>();
292         WSEncryptionPart encP =
293             new WSEncryptionPart(
294                 "foobar",
295                 "urn:foo.bar2",
296                 "");
297         parts.add(encP);
298         sign.setParts(parts);
299         
300         try {
301             sign.build(doc, crypto, secHeader);
302             fail("Failure expected on a bad namespace");
303         } catch (WSSecurityException ex) {
304             // expected
305         }
306     }
307     
308     /**
309      * Test signing a custom SOAP header and the SOAP body
310      */
311     @org.junit.Test
312     public void testSOAPHeaderAndBody() throws Exception {
313         WSSecSignature sign = new WSSecSignature();
314         sign.setUserInfo("16c73ab6-b892-458f-abf5-2f875f74882e", "security");
315         sign.setKeyIdentifierType(WSConstants.ISSUER_SERIAL);
316 
317         Document doc = SOAPUtil.toSOAPPart(SOAPMSG);
318         SOAPConstants soapConstants = 
319             WSSecurityUtil.getSOAPConstants(doc.getDocumentElement());
320 
321         WSSecHeader secHeader = new WSSecHeader();
322         secHeader.insertSecurityHeader(doc);
323         
324         List<WSEncryptionPart> parts = new ArrayList<WSEncryptionPart>();
325         WSEncryptionPart encP =
326             new WSEncryptionPart(
327                 soapConstants.getBodyQName().getLocalPart(),    // define the body
328                 soapConstants.getEnvelopeURI(),
329                 "");
330         parts.add(encP);
331         WSEncryptionPart encP2 =
332             new WSEncryptionPart(
333                 "foobar",
334                 "urn:foo.bar",
335                 "");
336         parts.add(encP2);
337         sign.setParts(parts);
338         
339         Document signedDoc = sign.build(doc, crypto, secHeader);
340         
341         if (LOG.isDebugEnabled()) {
342             String outputString = 
343                 org.apache.ws.security.util.XMLUtils.PrettyDocumentToString(signedDoc);
344             LOG.debug(outputString);
345         }
346         
347         List<WSSecurityEngineResult> results = verify(signedDoc);
348         
349         QName fooName = new QName("urn:foo.bar", "foobar");
350         QName bodyName = new QName(soapConstants.getEnvelopeURI(), "Body");
351         WSSecurityUtil.checkAllElementsProtected(results, WSConstants.SIGN, new QName[]{fooName});
352         WSSecurityUtil.checkAllElementsProtected(results, WSConstants.SIGN, new QName[]{bodyName});
353         WSSecurityUtil.checkAllElementsProtected(
354             results, 
355             WSConstants.SIGN, 
356             new QName[]{bodyName, fooName}
357         );
358         WSSecurityUtil.checkAllElementsProtected(
359             results, 
360             WSConstants.SIGN, 
361             new QName[]{fooName, bodyName}
362         );
363         try {
364             WSSecurityUtil.checkAllElementsProtected(
365                 results, 
366                 WSConstants.ENCR, 
367                 new QName[]{fooName, bodyName}
368             );
369             fail("Failure expected on a wrong action");
370         } catch (WSSecurityException ex) {
371             // expected
372         }
373         try {
374             QName headerName = new QName(soapConstants.getEnvelopeURI(), "Header");
375             WSSecurityUtil.checkAllElementsProtected(
376                 results, 
377                 WSConstants.SIGN, 
378                 new QName[]{fooName, bodyName, headerName}
379             );
380             fail("Failure expected on an unsatisfied requirement");
381         } catch (WSSecurityException ex) {
382             // expected
383         }
384     }
385     
386     
387     /**
388      * Test getting a DOM Element from WSEncryptionPart directly
389      */
390     @org.junit.Test
391     public void testSignaturePartDOMElement() throws Exception {
392         WSSecSignature sign = new WSSecSignature();
393         sign.setUserInfo("16c73ab6-b892-458f-abf5-2f875f74882e", "security");
394         sign.setKeyIdentifierType(WSConstants.ISSUER_SERIAL);
395 
396         Document doc = SOAPUtil.toSOAPPart(SOAPMSG);
397         SOAPConstants soapConstants = 
398             WSSecurityUtil.getSOAPConstants(doc.getDocumentElement());
399 
400         WSSecHeader secHeader = new WSSecHeader();
401         secHeader.insertSecurityHeader(doc);
402         
403         List<WSEncryptionPart> parts = new ArrayList<WSEncryptionPart>();
404         // Give wrong names to make sure it's picking up the element
405         WSEncryptionPart encP =
406             new WSEncryptionPart(
407                 "Incorrect Localname",
408                 "Incorrect N/S",
409                 "");
410         Element bodyElement = WSSecurityUtil.findBodyElement(doc);
411         assertTrue(bodyElement != null && "Body".equals(bodyElement.getLocalName()));
412         encP.setElement(bodyElement);
413         parts.add(encP);
414         sign.setParts(parts);
415         
416         Document signedDoc = sign.build(doc, crypto, secHeader);
417         
418         if (LOG.isDebugEnabled()) {
419             String outputString = 
420                 org.apache.ws.security.util.XMLUtils.PrettyDocumentToString(signedDoc);
421             LOG.debug(outputString);
422         }
423         
424         List<WSSecurityEngineResult> results = verify(signedDoc);
425         
426         QName bodyName = new QName(soapConstants.getEnvelopeURI(), "Body");
427         WSSecurityUtil.checkAllElementsProtected(results, WSConstants.SIGN, new QName[]{bodyName});
428     }
429     
430     /**
431      * Test signing two SOAP Body elements with the same QName.
432      */
433     @org.junit.Test
434     public void testMultipleElements() throws Exception {
435         Document doc = SOAPUtil.toSOAPPart(SOAPMSG_MULTIPLE);
436         WSSecSignature sign = new WSSecSignature();
437         sign.setUserInfo("16c73ab6-b892-458f-abf5-2f875f74882e", "security");
438         sign.setKeyIdentifierType(WSConstants.ISSUER_SERIAL);
439 
440         WSSecHeader secHeader = new WSSecHeader();
441         secHeader.insertSecurityHeader(doc);
442         
443         List<WSEncryptionPart> parts = new ArrayList<WSEncryptionPart>();
444         WSEncryptionPart encP =
445             new WSEncryptionPart(
446                 "testMethod",
447                 "http://axis/service/security/test6/LogTestService8",
448                 "");
449         parts.add(encP);
450         sign.setParts(parts);
451         
452         Document signedDoc = sign.build(doc, crypto, secHeader);
453         
454         String outputString = 
455             org.apache.ws.security.util.XMLUtils.PrettyDocumentToString(signedDoc);
456         if (LOG.isDebugEnabled()) {
457             LOG.debug(outputString);
458         }
459         
460         verify(signedDoc);
461     }
462     
463 
464     /**
465      * Verifies the soap envelope
466      * <p/>
467      * 
468      * @param doc 
469      * @throws Exception Thrown when there is a problem in verification
470      */
471     private List<WSSecurityEngineResult> verify(Document doc) throws Exception {
472         List<WSSecurityEngineResult> results = 
473             secEngine.processSecurityHeader(doc, null, null, crypto);
474         if (LOG.isDebugEnabled()) {
475             LOG.debug("Verfied and decrypted message:");
476             String outputString = 
477                 org.apache.ws.security.util.XMLUtils.PrettyDocumentToString(doc);
478             LOG.debug(outputString);
479         }
480         return results;
481     }
482 
483 }