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.engine.WSSConfig;
27  import org.apache.wss4j.dom.engine.WSSecurityEngine;
28  import org.apache.wss4j.dom.engine.WSSecurityEngineResult;
29  import org.apache.wss4j.common.crypto.Crypto;
30  import org.apache.wss4j.common.crypto.CryptoFactory;
31  import org.apache.wss4j.common.crypto.Merlin;
32  import org.apache.wss4j.common.ext.WSSecurityException;
33  import org.apache.wss4j.common.util.XMLUtils;
34  import org.apache.wss4j.dom.handler.HandlerAction;
35  import org.apache.wss4j.dom.handler.RequestData;
36  import org.apache.wss4j.dom.handler.WSHandlerConstants;
37  import org.apache.wss4j.dom.handler.WSHandlerResult;
38  
39  import org.bouncycastle.jce.provider.BouncyCastleProvider;
40  import org.junit.jupiter.api.Test;
41  import org.junit.jupiter.params.ParameterizedTest;
42  import org.junit.jupiter.params.provider.CsvSource;
43  import org.w3c.dom.Document;
44  
45  import javax.security.auth.x500.X500Principal;
46  import java.security.Security;
47  import java.security.cert.X509Certificate;
48  import java.util.Collections;
49  import java.util.Properties;
50  
51  import static org.junit.jupiter.api.Assertions.*;
52  
53  /**
54   * This is a test for WSS-40. Essentially it just tests that a message is signed using a
55   * keyEntry from one keystore, and verified at the other end with a keystore with just the
56   * CA cert in it.
57   *
58   * http://issues.apache.org/jira/browse/WSS-40
59   *
60   * Generate the CA keys/certs + export the CA cert to a keystore
61   *
62   * openssl req -x509 -newkey rsa:2048 -keyout wss40CAKey.pem -out wss40CA.pem
63   * -config ca.config -days 3650
64   * openssl x509 -outform DER -in wss40CA.pem -out wss40CA.crt
65   * keytool -import -file wss40CA.crt -alias wss40CA -keystore wss40CA.jks
66   *
67   * Generate the client keypair, make a csr, sign it with the CA key
68   *
69   * keytool -genkey -validity 3650 -alias wss40 -keyalg RSA -keystore wss40.jks
70   * -dname "CN=Colm,OU=WSS4J,O=Apache,L=Dublin,ST=Leinster,C=IE"
71   * keytool -certreq -alias wss40 -keystore wss40.jks -file wss40.cer
72   * openssl ca -config ca.config -policy policy_anything -days 3650 -out wss40.pem -infiles wss40.cer
73   * openssl x509 -outform DER -in wss40.pem -out wss40.crt
74   *
75   * Import the CA cert into wss40.jks and import the new signed certificate
76   *
77   * keytool -import -file wss40CA.crt -alias wss40CA -keystore wss40.jks
78   * keytool -import -file wss40.crt -alias wss40 -keystore wss40.jks
79   *
80   */
81  public class SignatureCertTest {
82      private static final org.slf4j.Logger LOG =
83          org.slf4j.LoggerFactory.getLogger(SignatureCertTest.class);
84      private WSSecurityEngine secEngine = new WSSecurityEngine();
85      private Crypto crypto;
86      private Crypto cryptoCA;
87      private boolean isJDK16up;
88  
89      public SignatureCertTest() throws Exception {
90          WSSConfig.init();
91          crypto = CryptoFactory.getInstance("wss40.properties");
92          cryptoCA = CryptoFactory.getInstance("wss40CA.properties");
93          try {
94              int javaVersion = Integer.getInteger("java.specification.version", 0);
95              isJDK16up = javaVersion >= 16;
96          } catch (NumberFormatException ex) {
97              LOG.warn("Error in retrieving the java version: [{}]", ex.getMessage());
98          }
99      }
100 
101     /**
102      * Test signing a SOAP message using a BST.
103      */
104     @Test
105     public void testSignatureDirectReference() throws Exception {
106         Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
107         WSSecHeader secHeader = new WSSecHeader(doc);
108         secHeader.insertSecurityHeader();
109 
110         WSSecSignature sign = new WSSecSignature(secHeader);
111         sign.setUserInfo("wss40", "security");
112         sign.setKeyIdentifierType(WSConstants.BST_DIRECT_REFERENCE);
113 
114         Document signedDoc = sign.build(crypto);
115 
116         if (LOG.isDebugEnabled()) {
117             String outputString =
118                 XMLUtils.prettyDocumentToString(signedDoc);
119             LOG.debug(outputString);
120         }
121         //
122         // Verify the signature
123         //
124         WSHandlerResult results = verify(signedDoc, cryptoCA);
125         WSSecurityEngineResult result =
126             results.getActionResults().get(WSConstants.SIGN).get(0);
127         X509Certificate cert =
128             (X509Certificate)result.get(WSSecurityEngineResult.TAG_X509_CERTIFICATE);
129         assertNotNull(cert);
130     }
131 
132     /**
133      * Test signing a SOAP message using a BST, sending the CA cert as well in the
134      * message.
135      */
136     @Test
137     public void testSignatureDirectReferenceCACert() throws Exception {
138         Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
139         WSSecHeader secHeader = new WSSecHeader(doc);
140         secHeader.insertSecurityHeader();
141 
142         WSSecSignature sign = new WSSecSignature(secHeader);
143         sign.setUserInfo("wss40", "security");
144         sign.setKeyIdentifierType(WSConstants.BST_DIRECT_REFERENCE);
145         sign.setUseSingleCertificate(false);
146 
147         Document signedDoc = sign.build(crypto);
148 
149         if (LOG.isDebugEnabled()) {
150             String outputString =
151                 XMLUtils.prettyDocumentToString(signedDoc);
152             LOG.debug("BST CA Cert");
153             LOG.debug(outputString);
154         }
155         //
156         // Verify the signature
157         //
158         WSHandlerResult results = verify(signedDoc, cryptoCA);
159         WSSecurityEngineResult result =
160             results.getActionResults().get(WSConstants.SIGN).get(0);
161         X509Certificate cert =
162             (X509Certificate)result.get(WSSecurityEngineResult.TAG_X509_CERTIFICATE);
163         assertNotNull(cert);
164         X509Certificate[] certs =
165             (X509Certificate[])result.get(WSSecurityEngineResult.TAG_X509_CERTIFICATES);
166         assertTrue(certs != null && certs.length == 2);
167     }
168 
169 
170     /**
171      * Test signing a SOAP message using Issuer Serial. Note that this should fail, as the
172      * trust-store does not contain the cert corresponding to wss40, only the CA cert
173      * wss40CA.
174      */
175     @Test
176     public void testSignatureIssuerSerial() throws Exception {
177         Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
178         WSSecHeader secHeader = new WSSecHeader(doc);
179         secHeader.insertSecurityHeader();
180 
181         WSSecSignature sign = new WSSecSignature(secHeader);
182         sign.setUserInfo("wss40", "security");
183         sign.setKeyIdentifierType(WSConstants.ISSUER_SERIAL);
184 
185         Document signedDoc = sign.build(crypto);
186 
187         if (LOG.isDebugEnabled()) {
188             String outputString =
189                 XMLUtils.prettyDocumentToString(signedDoc);
190             LOG.debug(outputString);
191         }
192 
193         try {
194             verify(signedDoc, cryptoCA);
195             fail("Failure expected on issuer serial");
196         } catch (WSSecurityException ex) {
197             assertTrue(ex.getErrorCode() == WSSecurityException.ErrorCode.FAILED_CHECK);
198         }
199     }
200 
201 
202     /**
203      * Test signing a SOAP message using a BST. The signature verification passes, but the trust
204      * verification will fail as the CA cert is out of date.
205      */
206     @Test
207     public void testSignatureBadCACert() throws Exception {
208         Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
209         WSSecHeader secHeader = new WSSecHeader(doc);
210         secHeader.insertSecurityHeader();
211 
212         WSSecSignature sign = new WSSecSignature(secHeader);
213         sign.setUserInfo("wss40expca", "security");
214         sign.setKeyIdentifierType(WSConstants.BST_DIRECT_REFERENCE);
215 
216         Document signedDoc =
217             sign.build(CryptoFactory.getInstance("wss40badca.properties"));
218 
219         if (LOG.isDebugEnabled()) {
220             String outputString =
221                 XMLUtils.prettyDocumentToString(signedDoc);
222             LOG.debug(outputString);
223         }
224         //
225         // Verify the signature
226         //
227         try {
228             verify(signedDoc, CryptoFactory.getInstance("wss40badcatrust.properties"));
229             fail("Failure expected on bad CA cert!");
230         } catch (WSSecurityException ex) {
231             assertTrue(ex.getErrorCode() == WSSecurityException.ErrorCode.FAILURE);
232         }
233     }
234 
235     /**
236      * A test for "SignatureAction does not set DigestAlgorithm on WSSecSignature instance"
237      */
238     @Test
239     public void testMultipleCertsWSHandler() throws Exception {
240         final WSSConfig cfg = WSSConfig.getNewInstance();
241         final RequestData reqData = new RequestData();
242         reqData.setWssConfig(cfg);
243         reqData.setUsername("wss40");
244         java.util.Map<String, String> config = new java.util.TreeMap<>();
245         config.put(WSHandlerConstants.SIG_PROP_FILE, "wss40.properties");
246         config.put("password", "security");
247         config.put(WSHandlerConstants.SIG_KEY_ID, "DirectReference");
248         config.put(WSHandlerConstants.USE_SINGLE_CERTIFICATE, "false");
249         reqData.setMsgContext(config);
250 
251         final Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
252         CustomHandler handler = new CustomHandler();
253         HandlerAction action = new HandlerAction(WSConstants.SIGN);
254         handler.send(
255             doc,
256             reqData,
257             Collections.singletonList(action),
258             true
259         );
260 
261         //
262         // Verify the signature
263         //
264         WSHandlerResult results = verify(doc, cryptoCA);
265         WSSecurityEngineResult result =
266             results.getActionResults().get(WSConstants.SIGN).get(0);
267         X509Certificate cert =
268             (X509Certificate)result.get(WSSecurityEngineResult.TAG_X509_CERTIFICATE);
269         assertNotNull(cert);
270         X509Certificate[] certs =
271             (X509Certificate[])result.get(WSSecurityEngineResult.TAG_X509_CERTIFICATES);
272         assertTrue(certs != null && certs.length == 2);
273     }
274 
275     @Test
276     public void testExpiredCert() throws Exception {
277         Properties clientProperties = new Properties();
278         clientProperties.put("org.apache.wss4j.crypto.provider",
279                 "org.apache.wss4j.common.crypto.Merlin");
280         clientProperties.put("org.apache.wss4j.crypto.merlin.keystore.type", "jks");
281         clientProperties.put("org.apache.wss4j.crypto.merlin.keystore.password", "security");
282         clientProperties.put("org.apache.wss4j.crypto.merlin.keystore.alias", "wss40exp");
283         clientProperties.put("org.apache.wss4j.crypto.merlin.keystore.file", "keys/wss40exp.jks");
284 
285         Crypto clientCrypto = new Merlin(clientProperties, this.getClass().getClassLoader(), null);
286 
287         Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
288         WSSecHeader secHeader = new WSSecHeader(doc);
289         secHeader.insertSecurityHeader();
290 
291         WSSecSignature sign = new WSSecSignature(secHeader);
292         sign.setUserInfo("wss40exp", "security");
293         sign.setKeyIdentifierType(WSConstants.BST_DIRECT_REFERENCE);
294 
295         Document signedDoc = sign.build(clientCrypto);
296 
297         if (LOG.isDebugEnabled()) {
298             String outputString =
299                 XMLUtils.prettyDocumentToString(signedDoc);
300             LOG.debug(outputString);
301         }
302         //
303         // Verify the signature
304         //
305         WSSecurityEngine newEngine = new WSSecurityEngine();
306         try {
307             newEngine.processSecurityHeader(doc, null, null, cryptoCA);
308             fail("Failure expected on an expired cert");
309         } catch (WSSecurityException ex) {
310             assertTrue(ex.getErrorCode() == WSSecurityException.ErrorCode.FAILURE);
311         }
312     }
313 
314     @Test
315     public void testExpiredCertInKeystore() throws Exception {
316         Properties clientProperties = new Properties();
317         clientProperties.put("org.apache.wss4j.crypto.provider",
318                 "org.apache.wss4j.common.crypto.Merlin");
319         clientProperties.put("org.apache.wss4j.crypto.merlin.keystore.type", "jks");
320         clientProperties.put("org.apache.wss4j.crypto.merlin.keystore.password", "security");
321         clientProperties.put("org.apache.wss4j.crypto.merlin.keystore.alias", "wss40exp");
322         clientProperties.put("org.apache.wss4j.crypto.merlin.keystore.file", "keys/wss40exp.jks");
323 
324         Crypto clientCrypto = new Merlin(clientProperties, this.getClass().getClassLoader(), null);
325 
326         Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
327         WSSecHeader secHeader = new WSSecHeader(doc);
328         secHeader.insertSecurityHeader();
329 
330         WSSecSignature sign = new WSSecSignature(secHeader);
331         sign.setUserInfo("wss40exp", "security");
332         sign.setKeyIdentifierType(WSConstants.BST_DIRECT_REFERENCE);
333 
334         Document signedDoc = sign.build(clientCrypto);
335 
336         if (LOG.isDebugEnabled()) {
337             String outputString =
338                 XMLUtils.prettyDocumentToString(signedDoc);
339             LOG.debug(outputString);
340         }
341         //
342         // Verify the signature
343         //
344         WSSecurityEngine newEngine = new WSSecurityEngine();
345         try {
346             newEngine.processSecurityHeader(doc, null, null, clientCrypto);
347             fail("Failure expected on an expired cert");
348         } catch (WSSecurityException ex) {
349             assertTrue(ex.getErrorCode() == WSSecurityException.ErrorCode.FAILED_CHECK);
350         }
351     }
352 
353     /**
354      * The EdDSA KeyValue test.
355      */
356     @ParameterizedTest
357     @CsvSource({
358             "ed25519, 'Algorithm=\"http://www.w3.org/2021/04/xmldsig-more#eddsa-ed25519\"', 'CN=ed25519, OU=eDeliveryAS4-2.0, OU=wss4j, O=apache, C=EU'",
359             "ed448, 'Algorithm=\"http://www.w3.org/2021/04/xmldsig-more#eddsa-ed448\"', 'CN=ed448, OU=eDeliveryAS4-2.0, OU=wss4j, O=apache, C=EU'",
360         })
361     public void testEdDSASignatureDirectReference(String alias, String algorithm, X500Principal certSubjectDN) throws Exception {
362         try {
363             // not needed after JDK 16
364             if (!isJDK16up) {
365                 Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
366             }
367 
368             Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
369             WSSecHeader secHeader = new WSSecHeader(doc);
370             secHeader.insertSecurityHeader();
371 
372             Crypto ed_crypto = CryptoFactory.getInstance("wss-eddsa.properties");
373 
374             WSSecSignature builder = new WSSecSignature(secHeader);
375             builder.setUserInfo(alias, "security");
376             builder.setKeyIdentifierType(WSConstants.BST_DIRECT_REFERENCE);
377             Document signedDoc = builder.build(ed_crypto);
378             // test the algorithm attribute
379             String outputString =
380                     XMLUtils.prettyDocumentToString(signedDoc);
381             if (LOG.isDebugEnabled()) {
382                 LOG.debug(outputString);
383             }
384 
385             assertTrue(outputString.contains(algorithm));
386 
387             final WSHandlerResult results = verify(signedDoc, ed_crypto);
388 
389             WSSecurityEngineResult actionResult =
390                     results.getActionResults().get(WSConstants.SIGN).get(0);
391             assertNotNull(actionResult);
392 
393             java.security.Principal principal =
394                     (java.security.Principal) actionResult.get(WSSecurityEngineResult.TAG_PRINCIPAL);
395             assertTrue(principal instanceof X500Principal);
396             X500Principal x500Principal = (X500Principal) principal;
397             assertEquals(certSubjectDN, x500Principal);
398 
399         } finally {
400             if (!isJDK16up) {
401                 Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
402             }
403         }
404     }
405 
406     /**
407      * Verifies the soap envelope
408      * <p/>
409      *
410      * @param doc
411      * @throws Exception Thrown when there is a problem in verification
412      */
413     private WSHandlerResult verify(Document doc, Crypto crypto) throws Exception {
414         WSHandlerResult results = secEngine.processSecurityHeader(
415             doc, null, null, crypto
416         );
417         if (LOG.isDebugEnabled()) {
418             LOG.debug("Verfied and decrypted message:");
419             String outputString =
420                 XMLUtils.prettyDocumentToString(doc);
421             LOG.debug(outputString);
422         }
423         return results;
424     }
425 
426 
427 }