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    * <p>
10   * http://www.apache.org/licenses/LICENSE-2.0
11   * <p>
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.common.crypto;
21  
22  import static org.hamcrest.MatcherAssert.assertThat;
23  import static org.hamcrest.core.Is.is;
24  import static org.hamcrest.core.IsEqual.equalTo;
25  
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.security.KeyStore;
29  import java.security.Security;
30  import java.security.cert.Certificate;
31  import java.security.cert.TrustAnchor;
32  import java.security.cert.X509Certificate;
33  import java.util.ArrayList;
34  import java.util.Collections;
35  import java.util.Enumeration;
36  import java.util.List;
37  import java.util.Properties;
38  import java.util.regex.Pattern;
39  
40  import org.apache.wss4j.common.ext.WSSecurityException;
41  import org.apache.wss4j.common.util.Loader;
42  import org.junit.jupiter.api.Assertions;
43  import org.junit.jupiter.api.BeforeEach;
44  import org.junit.jupiter.api.Test;
45  
46  import static org.junit.jupiter.api.Assertions.assertNotNull;
47  import static org.junit.jupiter.api.Assertions.assertNull;
48  import static org.junit.jupiter.api.Assumptions.assumeFalse;
49  
50  /**
51   * Tests the handling of {@code NameConstraint}s with {@code TrustAnchor}s in the
52   * {@link Merlin}, {@link MerlinAKI}, and {@link CertificateStore} crypto implementations.
53   * Specifically tests the following:
54   * <ul>
55   * <li>That when Name Constraints are extracted from a certificate they are correctly
56   * decoded into a SEQUENCE</li>
57   * <li>That when the new property {@code org.apache.wss4j.crypto.merlin.cert.provider.nameconstraints}
58   * is set to true on the Merlin and MerlinAKI implementations the Trust Anchors constructed
59   * for path validation have the Name Constraints added</li>
60   * <li>That when the above property is <em>not</em> set, the Trust Anchors have
61   * null Name Constraints added</li>
62   * </ul>
63   */
64  public class NameConstraintsTest {
65      private static final String KEY_ROOT = "keys/nameconstraints/";
66  
67      private static final String SELF_SIGNED = KEY_ROOT + "self_signed.p12";
68  
69      private static final String ROOT_SIGNED = KEY_ROOT + "root_signed.p12";
70  
71      private static final String INTERMEDIATE_SIGNED = KEY_ROOT + "intermediate_signed.p12";
72  
73      private static final String KEYSTORE = KEY_ROOT + "nameconstraints.jks";
74  
75      private static final char[] PASSWORD = "changeit".toCharArray();
76  
77      private static final Pattern SUBJ_PATTERN = Pattern.compile(".*OU=wss4j,O=apache");
78  
79      private boolean isIBMJdK = System.getProperty("java.vendor").contains("IBM");
80  
81      @BeforeEach
82      public void setup() throws Exception {
83          WSProviderConfig.init();
84      }
85  
86      private KeyStore getRootKeyStore() throws Exception {
87          ClassLoader loader = Loader.getClassLoader(NameConstraintsTest.class);
88          KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
89  
90          try (InputStream inputStream = Merlin.loadInputStream(loader, KEYSTORE)) {
91              keyStore.load(inputStream, PASSWORD);
92              return keyStore;
93          }
94      }
95  
96      private KeyStore getSelfKeyStore() throws Exception {
97          ClassLoader loader = Loader.getClassLoader(NameConstraintsTest.class);
98          KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
99  
100         try (InputStream inputStream = loader.getResourceAsStream(SELF_SIGNED)) {
101             keyStore.load(inputStream, PASSWORD);
102             return keyStore;
103         }
104     }
105 
106     private X509Certificate[] getTestCertificateChain(String keychainPath) throws Exception {
107         ClassLoader loader = Loader.getClassLoader(NameConstraintsTest.class);
108         KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
109 
110         try (InputStream inputStream = loader.getResourceAsStream(keychainPath)) {
111             keystore.load(inputStream, PASSWORD);
112 
113             // We're loading a single cert chain; there will be one alias
114             Enumeration<String> aliases = keystore.aliases();
115             Certificate[] certificates = keystore.getCertificateChain(aliases.nextElement());
116             assertNotNull(certificates);
117 
118             X509Certificate[] x509Certificates = new X509Certificate[certificates.length];
119             System.arraycopy(certificates, 0, x509Certificates, 0, certificates.length);
120 
121             return x509Certificates;
122         }
123     }
124 
125     @Test
126     public void testNameConstraints() throws Exception {
127         assumeFalse(isIBMJdK);
128 
129         Merlin merlin = new Merlin();
130         X509Certificate[] certificates = getTestCertificateChain(INTERMEDIATE_SIGNED);
131 
132         assertThat(merlin.getNameConstraints(certificates[0]).length, equalTo(0));
133         assertThat(merlin.getNameConstraints(certificates[1]).length, equalTo(0));
134 
135         byte[] nameConstraints = merlin.getNameConstraints(certificates[2]);
136         assertNotNull(nameConstraints);
137         assertThat("Tag byte is wrong", nameConstraints[0], is(DERDecoder.TYPE_SEQUENCE));
138 
139         TrustAnchor trustAnchor = new TrustAnchor(certificates[2], nameConstraints);
140         assertThat("TrustAnchor constraints wrong",
141                 trustAnchor.getNameConstraints(),
142                 equalTo(nameConstraints));
143     }
144 
145     @Test
146     public void testNameConstraintsWithKeyStoreUsingMerlin() throws Exception {
147         assumeFalse(isIBMJdK);
148 
149         withKeyStoreUsingMerlin(getSelfKeyStore(),
150                 getTestCertificateChain(SELF_SIGNED),
151                 new Merlin());
152         withKeyStoreUsingMerlin(getRootKeyStore(),
153                 getTestCertificateChain(ROOT_SIGNED),
154                 new Merlin());
155         withKeyStoreUsingMerlin(getRootKeyStore(),
156                 getTestCertificateChain(INTERMEDIATE_SIGNED),
157                 new Merlin());
158     }
159 
160     @Test
161     public void testNameConstraintsWithTrustStoreUsingMerlin() throws Exception {
162         assumeFalse(isIBMJdK);
163 
164         withTrustStoreUsingMerlin(getSelfKeyStore(),
165                 getTestCertificateChain(SELF_SIGNED),
166                 new Merlin());
167         withTrustStoreUsingMerlin(getRootKeyStore(),
168                 getTestCertificateChain(ROOT_SIGNED),
169                 new Merlin());
170         withTrustStoreUsingMerlin(getRootKeyStore(),
171                 getTestCertificateChain(INTERMEDIATE_SIGNED),
172                 new Merlin());
173     }
174 
175     @Test
176     public void testNameConstraintsWithKeyStoreUsingMerlinAki() throws Exception {
177         assumeFalse(isIBMJdK);
178 
179         withKeyStoreUsingMerlinAKI(getSelfKeyStore(),
180                 getTestCertificateChain(SELF_SIGNED),
181                 new MerlinAKI());
182         withKeyStoreUsingMerlinAKI(getRootKeyStore(),
183                 getTestCertificateChain(ROOT_SIGNED),
184                 new MerlinAKI());
185         withKeyStoreUsingMerlinAKI(getRootKeyStore(),
186                 getTestCertificateChain(INTERMEDIATE_SIGNED),
187                 new MerlinAKI());
188     }
189 
190     @Test
191     public void testNameConstraintsWithTrustStoreUsingMerlinAki() throws Exception {
192         assumeFalse(isIBMJdK);
193 
194         withTrustStoreUsingMerlinAKI(getSelfKeyStore(),
195                 getTestCertificateChain(SELF_SIGNED),
196                 new MerlinAKI());
197         withTrustStoreUsingMerlinAKI(getRootKeyStore(),
198                 getTestCertificateChain(ROOT_SIGNED),
199                 new MerlinAKI());
200         withTrustStoreUsingMerlinAKI(getRootKeyStore(),
201                 getTestCertificateChain(INTERMEDIATE_SIGNED),
202                 new MerlinAKI());
203     }
204 
205     @Test
206     public void testNameConstraintsWithKeyStoreUsingMerlinBc() throws Exception {
207         assumeFalse(isIBMJdK);
208 
209         withKeyStoreUsingMerlin(getSelfKeyStore(),
210                 getTestCertificateChain(SELF_SIGNED),
211                 getMerlinBc());
212         withKeyStoreUsingMerlin(getRootKeyStore(),
213                 getTestCertificateChain(ROOT_SIGNED),
214                 getMerlinBc());
215         withKeyStoreUsingMerlin(getRootKeyStore(),
216                 getTestCertificateChain(INTERMEDIATE_SIGNED),
217                 getMerlinBc());
218     }
219 
220     @Test
221     public void testNameConstraintsWithTrustStoreUsingMerlinBc() throws Exception {
222         assumeFalse(isIBMJdK);
223 
224         withTrustStoreUsingMerlin(getSelfKeyStore(),
225                 getTestCertificateChain(SELF_SIGNED),
226                 getMerlinBc());
227         withTrustStoreUsingMerlin(getRootKeyStore(),
228                 getTestCertificateChain(ROOT_SIGNED),
229                 getMerlinBc());
230         withTrustStoreUsingMerlin(getRootKeyStore(),
231                 getTestCertificateChain(INTERMEDIATE_SIGNED),
232                 getMerlinBc());
233     }
234 
235     @Test
236     public void testNameConstraintsWithKeyStoreUsingMerlinAkiBc() throws Exception {
237         assumeFalse(isIBMJdK);
238 
239         withKeyStoreUsingMerlinAKI(getSelfKeyStore(),
240                 getTestCertificateChain(SELF_SIGNED),
241                 getMerlinAkiBc());
242         withKeyStoreUsingMerlinAKI(getRootKeyStore(),
243                 getTestCertificateChain(ROOT_SIGNED),
244                 getMerlinAkiBc());
245         withKeyStoreUsingMerlinAKI(getRootKeyStore(),
246                 getTestCertificateChain(INTERMEDIATE_SIGNED),
247                 getMerlinAkiBc());
248     }
249 
250     @Test
251     public void testNameConstraintsWithTrustStoreUsingMerlinAkiBc() throws Exception {
252         assumeFalse(isIBMJdK);
253 
254         withTrustStoreUsingMerlinAKI(getSelfKeyStore(),
255                 getTestCertificateChain(SELF_SIGNED),
256                 getMerlinAkiBc());
257         withTrustStoreUsingMerlinAKI(getRootKeyStore(),
258                 getTestCertificateChain(ROOT_SIGNED),
259                 getMerlinAkiBc());
260         withTrustStoreUsingMerlinAKI(getRootKeyStore(),
261                 getTestCertificateChain(INTERMEDIATE_SIGNED),
262                 getMerlinAkiBc());
263     }
264 
265     @Test
266     public void testNameConstraintsWithKeyStoreUsingMerlinBreaking() throws Exception {
267         assumeFalse(isIBMJdK);
268 
269         Properties properties = new Properties();
270         properties.setProperty("org.apache.wss4j.crypto.merlin.cert.provider.nameconstraints",
271                 "true");
272 
273         Merlin merlin = new Merlin(properties,
274                 this.getClass()
275                         .getClassLoader(),
276                 null);
277 
278         Assertions.assertThrows(Exception.class, () -> {
279             withKeyStoreUsingMerlin(getRootKeyStore(), getTestCertificateChain(ROOT_SIGNED), merlin);
280         });
281     }
282 
283     @Test
284     public void testNameConstraintsWithKeyStoreUsingMerlinAkiBreaking() throws Exception {
285         assumeFalse(isIBMJdK);
286 
287         Properties properties = new Properties();
288         properties.setProperty("org.apache.wss4j.crypto.merlin.cert.provider.nameconstraints",
289                 "true");
290 
291         MerlinAKI merlin = new MerlinAKI(properties,
292                 this.getClass()
293                         .getClassLoader(),
294                 null);
295 
296         Assertions.assertThrows(Exception.class, () -> {
297             withKeyStoreUsingMerlin(getRootKeyStore(), getTestCertificateChain(ROOT_SIGNED), merlin);
298         });
299     }
300 
301     @Test
302     public void testNameConstraintsUsingCertificateStore() throws Exception {
303         assumeFalse(isIBMJdK);
304 
305         usingCertificateStore(getSelfKeyStore(), getTestCertificateChain(SELF_SIGNED));
306         usingCertificateStore(getRootKeyStore(), getTestCertificateChain(ROOT_SIGNED));
307         usingCertificateStore(getRootKeyStore(), getTestCertificateChain(INTERMEDIATE_SIGNED));
308     }
309 
310     private void withKeyStoreUsingMerlin(KeyStore keyStore, X509Certificate[] certificates,
311             Merlin crypto) throws Exception {
312         // Load the keystore
313         crypto.setKeyStore(keyStore);
314 
315         crypto.verifyTrust(certificates, false, Collections.singletonList(SUBJ_PATTERN));
316         // No WSSecurityException thrown
317     }
318 
319     private void withTrustStoreUsingMerlin(KeyStore keyStore, X509Certificate[] certificates,
320             Merlin crypto) throws Exception {
321         // Load the keystore
322         crypto.setTrustStore(keyStore);
323 
324         crypto.verifyTrust(certificates, false, Collections.singletonList(SUBJ_PATTERN));
325         // No WSSecurityException thrown
326     }
327 
328     private void withKeyStoreUsingMerlinAKI(KeyStore keyStore, X509Certificate[] certificates,
329             MerlinAKI crypto) throws Exception {
330         // Load the keystore
331         crypto.setKeyStore(keyStore);
332 
333         crypto.verifyTrust(certificates, false, Collections.singletonList(SUBJ_PATTERN));
334         // No WSSecurityException thrown
335     }
336 
337     private void withTrustStoreUsingMerlinAKI(KeyStore keyStore, X509Certificate[] certificates,
338             MerlinAKI crypto) throws Exception {
339         // Load the keystore
340         crypto.setTrustStore(keyStore);
341 
342         crypto.verifyTrust(certificates, false, Collections.singletonList(SUBJ_PATTERN));
343         // No WSSecurityException thrown
344     }
345 
346     private void usingCertificateStore(KeyStore keyStore, X509Certificate[] certificates)
347             throws Exception {
348         // Load the keystore
349         Enumeration<String> aliases = keyStore.aliases();
350         List<X509Certificate> certList = new ArrayList<>();
351         while (aliases.hasMoreElements()) {
352             String alias = aliases.nextElement();
353             certList.add((X509Certificate) keyStore.getCertificate(alias));
354         }
355 
356         CertificateStore crypto = new CertificateStore(certList.toArray(new X509Certificate[] {}));
357 
358         crypto.verifyTrust(certificates, false, Collections.singletonList(SUBJ_PATTERN));
359         // No WSSecurityException thrown
360     }
361 
362     private Merlin getMerlinBc() throws WSSecurityException, IOException {
363         Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
364         Properties properties = new Properties();
365         properties.setProperty("org.apache.wss4j.crypto.merlin.cert.provider", "BC");
366         properties.setProperty("org.apache.wss4j.crypto.merlin.cert.provider.nameconstraints",
367                 "true");
368 
369         return new Merlin(properties,
370                 this.getClass()
371                         .getClassLoader(),
372                 null);
373     }
374 
375     private MerlinAKI getMerlinAkiBc() throws WSSecurityException, IOException {
376         Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
377         Properties properties = new Properties();
378         properties.setProperty("org.apache.wss4j.crypto.merlin.cert.provider", "BC");
379         properties.setProperty("org.apache.wss4j.crypto.merlin.cert.provider.nameconstraints",
380                 "true");
381 
382         return new MerlinAKI(properties,
383                 this.getClass()
384                         .getClassLoader(),
385                 null);
386     }
387 }