001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.io.crypto.tls;
019
020import static org.hamcrest.MatcherAssert.assertThat;
021import static org.hamcrest.Matchers.containsString;
022import static org.hamcrest.Matchers.equalTo;
023import static org.junit.Assert.assertArrayEquals;
024import static org.junit.Assert.assertEquals;
025import static org.junit.Assert.assertFalse;
026import static org.junit.Assert.assertThrows;
027import static org.junit.Assert.assertTrue;
028import static org.junit.Assume.assumeThat;
029import static org.mockito.Mockito.mock;
030
031import java.security.Security;
032import java.util.Arrays;
033import java.util.Collections;
034import org.apache.hadoop.hbase.HBaseClassTestRule;
035import org.apache.hadoop.hbase.exceptions.KeyManagerException;
036import org.apache.hadoop.hbase.exceptions.SSLContextException;
037import org.apache.hadoop.hbase.exceptions.TrustManagerException;
038import org.apache.hadoop.hbase.testclassification.SecurityTests;
039import org.apache.hadoop.hbase.testclassification.SmallTests;
040import org.junit.ClassRule;
041import org.junit.Test;
042import org.junit.experimental.categories.Category;
043import org.junit.runner.RunWith;
044import org.junit.runners.Parameterized;
045
046import org.apache.hbase.thirdparty.io.netty.buffer.ByteBufAllocator;
047import org.apache.hbase.thirdparty.io.netty.handler.ssl.SslContext;
048
049/**
050 * This file has been copied from the Apache ZooKeeper project.
051 * @see <a href=
052 *      "https://github.com/apache/zookeeper/blob/master/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509UtilTest.java">Base
053 *      revision</a>
054 */
055@RunWith(Parameterized.class)
056@Category({ SecurityTests.class, SmallTests.class })
057public class TestX509Util extends AbstractTestX509Parameterized {
058
059  @ClassRule
060  public static final HBaseClassTestRule CLASS_RULE =
061    HBaseClassTestRule.forClass(TestX509Util.class);
062
063  private static final char[] EMPTY_CHAR_ARRAY = new char[0];
064
065  @Test
066  public void testCreateSSLContextWithClientAuthDefault() throws Exception {
067    SslContext sslContext = X509Util.createSslContextForServer(conf);
068    ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class);
069    assertTrue(sslContext.newEngine(byteBufAllocatorMock).getNeedClientAuth());
070  }
071
072  @Test
073  public void testCreateSSLContextWithClientAuthNEED() throws Exception {
074    conf.set(X509Util.HBASE_SERVER_NETTY_TLS_CLIENT_AUTH_MODE, X509Util.ClientAuth.NEED.name());
075    SslContext sslContext = X509Util.createSslContextForServer(conf);
076    ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class);
077    assertTrue(sslContext.newEngine(byteBufAllocatorMock).getNeedClientAuth());
078  }
079
080  @Test
081  public void testCreateSSLContextWithClientAuthWANT() throws Exception {
082    conf.set(X509Util.HBASE_SERVER_NETTY_TLS_CLIENT_AUTH_MODE, X509Util.ClientAuth.WANT.name());
083    SslContext sslContext = X509Util.createSslContextForServer(conf);
084    ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class);
085    assertTrue(sslContext.newEngine(byteBufAllocatorMock).getWantClientAuth());
086  }
087
088  @Test
089  public void testCreateSSLContextWithClientAuthNONE() throws Exception {
090    conf.set(X509Util.HBASE_SERVER_NETTY_TLS_CLIENT_AUTH_MODE, X509Util.ClientAuth.NONE.name());
091    SslContext sslContext = X509Util.createSslContextForServer(conf);
092    ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class);
093    assertFalse(sslContext.newEngine(byteBufAllocatorMock).getNeedClientAuth());
094    assertFalse(sslContext.newEngine(byteBufAllocatorMock).getWantClientAuth());
095  }
096
097  @Test
098  public void testCreateSSLContextWithoutCustomProtocol() throws Exception {
099    SslContext sslContext = X509Util.createSslContextForClient(conf);
100    ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class);
101    assertArrayEquals(new String[] { X509Util.DEFAULT_PROTOCOL },
102      sslContext.newEngine(byteBufAllocatorMock).getEnabledProtocols());
103  }
104
105  @Test
106  public void testCreateSSLContextWithCustomProtocol() throws Exception {
107    final String protocol = "TLSv1.1";
108    conf.set(X509Util.TLS_CONFIG_PROTOCOL, protocol);
109    ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class);
110    SslContext sslContext = X509Util.createSslContextForServer(conf);
111    assertEquals(Collections.singletonList(protocol),
112      Arrays.asList(sslContext.newEngine(byteBufAllocatorMock).getEnabledProtocols()));
113  }
114
115  @Test(expected = SSLContextException.class)
116  public void testCreateSSLContextWithoutKeyStoreLocationServer() throws Exception {
117    conf.unset(X509Util.TLS_CONFIG_KEYSTORE_LOCATION);
118    X509Util.createSslContextForServer(conf);
119  }
120
121  @Test
122  public void testCreateSSLContextWithoutKeyStoreLocationClient() throws Exception {
123    conf.unset(X509Util.TLS_CONFIG_KEYSTORE_LOCATION);
124    X509Util.createSslContextForClient(conf);
125  }
126
127  @Test
128  public void testCreateSSLContextWithoutTrustStoreLocationClient() throws Exception {
129    conf.unset(X509Util.TLS_CONFIG_TRUSTSTORE_LOCATION);
130    X509Util.createSslContextForClient(conf);
131  }
132
133  @Test
134  public void testCreateSSLContextWithoutTrustStoreLocationServer() throws Exception {
135    conf.unset(X509Util.TLS_CONFIG_TRUSTSTORE_LOCATION);
136    X509Util.createSslContextForServer(conf);
137  }
138
139  // It would be great to test the value of PKIXBuilderParameters#setRevocationEnabled,
140  // but it does not appear to be possible
141  @Test
142  public void testCRLEnabled() throws Exception {
143    conf.setBoolean(X509Util.TLS_CONFIG_CLR, true);
144    X509Util.createSslContextForServer(conf);
145    assertTrue(Boolean.valueOf(System.getProperty("com.sun.net.ssl.checkRevocation")));
146    assertTrue(Boolean.valueOf(System.getProperty("com.sun.security.enableCRLDP")));
147    assertFalse(Boolean.valueOf(Security.getProperty("ocsp.enable")));
148  }
149
150  @Test
151  public void testCRLDisabled() throws Exception {
152    X509Util.createSslContextForServer(conf);
153    assertFalse(Boolean.valueOf(System.getProperty("com.sun.net.ssl.checkRevocation")));
154    assertFalse(Boolean.valueOf(System.getProperty("com.sun.security.enableCRLDP")));
155    assertFalse(Boolean.valueOf(Security.getProperty("ocsp.enable")));
156  }
157
158  @Test
159  public void testLoadPEMKeyStore() throws Exception {
160    // Make sure we can instantiate a key manager from the PEM file on disk
161    X509Util.createKeyManager(
162      x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
163      x509TestContext.getKeyStorePassword(), KeyStoreFileType.PEM.getPropertyValue());
164  }
165
166  @Test
167  public void testLoadPEMKeyStoreNullPassword() throws Exception {
168    assumeThat(x509TestContext.getKeyStorePassword(), equalTo(EMPTY_CHAR_ARRAY));
169    // Make sure that empty password and null password are treated the same
170    X509Util.createKeyManager(
171      x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(), null,
172      KeyStoreFileType.PEM.getPropertyValue());
173  }
174
175  @Test
176  public void testLoadPEMKeyStoreAutodetectStoreFileType() throws Exception {
177    // Make sure we can instantiate a key manager from the PEM file on disk
178    X509Util.createKeyManager(
179      x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
180      x509TestContext.getKeyStorePassword(),
181      null /* null StoreFileType means 'autodetect from file extension' */);
182  }
183
184  @Test(expected = KeyManagerException.class)
185  public void testLoadPEMKeyStoreWithWrongPassword() throws Exception {
186    // Attempting to load with the wrong key password should fail
187    X509Util.createKeyManager(
188      x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
189      "wrong password".toCharArray(), // intentionally use the wrong password
190      KeyStoreFileType.PEM.getPropertyValue());
191  }
192
193  @Test
194  public void testLoadPEMTrustStore() throws Exception {
195    // Make sure we can instantiate a trust manager from the PEM file on disk
196    X509Util.createTrustManager(
197      x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
198      x509TestContext.getTrustStorePassword(), KeyStoreFileType.PEM.getPropertyValue(), false,
199      false, true, true);
200  }
201
202  @Test
203  public void testLoadPEMTrustStoreNullPassword() throws Exception {
204    assumeThat(x509TestContext.getTrustStorePassword(), equalTo(EMPTY_CHAR_ARRAY));
205    // Make sure that empty password and null password are treated the same
206    X509Util.createTrustManager(
207      x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM).getAbsolutePath(), null,
208      KeyStoreFileType.PEM.getPropertyValue(), false, false, true, true);
209  }
210
211  @Test
212  public void testLoadPEMTrustStoreAutodetectStoreFileType() throws Exception {
213    // Make sure we can instantiate a trust manager from the PEM file on disk
214    X509Util.createTrustManager(
215      x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
216      x509TestContext.getTrustStorePassword(), null, // null StoreFileType means 'autodetect from
217                                                     // file extension'
218      false, false, true, true);
219  }
220
221  @Test
222  public void testLoadJKSKeyStore() throws Exception {
223    // Make sure we can instantiate a key manager from the JKS file on disk
224    X509Util.createKeyManager(
225      x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
226      x509TestContext.getKeyStorePassword(), KeyStoreFileType.JKS.getPropertyValue());
227  }
228
229  @Test
230  public void testLoadJKSKeyStoreNullPassword() throws Exception {
231    assumeThat(x509TestContext.getKeyStorePassword(), equalTo(EMPTY_CHAR_ARRAY));
232    // Make sure that empty password and null password are treated the same
233    X509Util.createKeyManager(
234      x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(), null,
235      KeyStoreFileType.JKS.getPropertyValue());
236  }
237
238  @Test
239  public void testLoadJKSKeyStoreAutodetectStoreFileType() throws Exception {
240    // Make sure we can instantiate a key manager from the JKS file on disk
241    X509Util.createKeyManager(
242      x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
243      x509TestContext.getKeyStorePassword(),
244      null /* null StoreFileType means 'autodetect from file extension' */);
245  }
246
247  @Test
248  public void testLoadJKSKeyStoreWithWrongPassword() {
249    assertThrows(KeyManagerException.class, () -> {
250      // Attempting to load with the wrong key password should fail
251      X509Util.createKeyManager(
252        x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
253        "wrong password".toCharArray(), KeyStoreFileType.JKS.getPropertyValue());
254    });
255  }
256
257  @Test
258  public void testLoadJKSTrustStore() throws Exception {
259    // Make sure we can instantiate a trust manager from the JKS file on disk
260    X509Util.createTrustManager(
261      x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
262      x509TestContext.getTrustStorePassword(), KeyStoreFileType.JKS.getPropertyValue(), true, true,
263      true, true);
264  }
265
266  @Test
267  public void testLoadJKSTrustStoreNullPassword() throws Exception {
268    assumeThat(x509TestContext.getTrustStorePassword(), equalTo(EMPTY_CHAR_ARRAY));
269    // Make sure that empty password and null password are treated the same
270    X509Util.createTrustManager(
271      x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(), null,
272      KeyStoreFileType.JKS.getPropertyValue(), false, false, true, true);
273  }
274
275  @Test
276  public void testLoadJKSTrustStoreAutodetectStoreFileType() throws Exception {
277    // Make sure we can instantiate a trust manager from the JKS file on disk
278    X509Util.createTrustManager(
279      x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
280      x509TestContext.getTrustStorePassword(), null, // null StoreFileType means 'autodetect from
281                                                     // file extension'
282      true, true, true, true);
283  }
284
285  @Test
286  public void testLoadJKSTrustStoreWithWrongPassword() {
287    assertThrows(TrustManagerException.class, () -> {
288      // Attempting to load with the wrong key password should fail
289      X509Util.createTrustManager(
290        x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
291        "wrong password".toCharArray(), KeyStoreFileType.JKS.getPropertyValue(), true, true, true,
292        true);
293    });
294  }
295
296  @Test
297  public void testLoadPKCS12KeyStore() throws Exception {
298    // Make sure we can instantiate a key manager from the PKCS12 file on disk
299    X509Util.createKeyManager(
300      x509TestContext.getKeyStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(),
301      x509TestContext.getKeyStorePassword(), KeyStoreFileType.PKCS12.getPropertyValue());
302  }
303
304  @Test
305  public void testLoadPKCS12KeyStoreNullPassword() throws Exception {
306    assumeThat(x509TestContext.getKeyStorePassword(), equalTo(EMPTY_CHAR_ARRAY));
307    // Make sure that empty password and null password are treated the same
308    X509Util.createKeyManager(
309      x509TestContext.getKeyStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(), null,
310      KeyStoreFileType.PKCS12.getPropertyValue());
311  }
312
313  @Test
314  public void testLoadPKCS12KeyStoreAutodetectStoreFileType() throws Exception {
315    // Make sure we can instantiate a key manager from the PKCS12 file on disk
316    X509Util.createKeyManager(
317      x509TestContext.getKeyStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(),
318      x509TestContext.getKeyStorePassword(),
319      null /* null StoreFileType means 'autodetect from file extension' */);
320  }
321
322  @Test
323  public void testLoadPKCS12KeyStoreWithWrongPassword() {
324    assertThrows(KeyManagerException.class, () -> {
325      // Attempting to load with the wrong key password should fail
326      X509Util.createKeyManager(
327        x509TestContext.getKeyStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(),
328        "wrong password".toCharArray(), KeyStoreFileType.PKCS12.getPropertyValue());
329    });
330  }
331
332  @Test
333  public void testLoadPKCS12TrustStore() throws Exception {
334    // Make sure we can instantiate a trust manager from the PKCS12 file on disk
335    X509Util.createTrustManager(
336      x509TestContext.getTrustStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(),
337      x509TestContext.getTrustStorePassword(), KeyStoreFileType.PKCS12.getPropertyValue(), true,
338      true, true, true);
339  }
340
341  @Test
342  public void testLoadPKCS12TrustStoreNullPassword() throws Exception {
343    assumeThat(x509TestContext.getTrustStorePassword(), equalTo(EMPTY_CHAR_ARRAY));
344    // Make sure that empty password and null password are treated the same
345    X509Util.createTrustManager(
346      x509TestContext.getTrustStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(), null,
347      KeyStoreFileType.PKCS12.getPropertyValue(), false, false, true, true);
348  }
349
350  @Test
351  public void testLoadPKCS12TrustStoreAutodetectStoreFileType() throws Exception {
352    // Make sure we can instantiate a trust manager from the PKCS12 file on disk
353    X509Util.createTrustManager(
354      x509TestContext.getTrustStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(),
355      x509TestContext.getTrustStorePassword(), null, // null StoreFileType means 'autodetect from
356                                                     // file extension'
357      true, true, true, true);
358  }
359
360  @Test
361  public void testLoadPKCS12TrustStoreWithWrongPassword() {
362    assertThrows(TrustManagerException.class, () -> {
363      // Attempting to load with the wrong key password should fail
364      X509Util.createTrustManager(
365        x509TestContext.getTrustStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(),
366        "wrong password".toCharArray(), KeyStoreFileType.PKCS12.getPropertyValue(), true, true,
367        true, true);
368    });
369  }
370
371  @Test
372  public void testGetDefaultCipherSuitesJava8() {
373    String[] cipherSuites = X509Util.getDefaultCipherSuitesForJavaVersion("1.8");
374    // Java 8 default should have the CBC suites first
375    assertThat(cipherSuites[0], containsString("CBC"));
376  }
377
378  @Test
379  public void testGetDefaultCipherSuitesJava9() {
380    String[] cipherSuites = X509Util.getDefaultCipherSuitesForJavaVersion("9");
381    // Java 9+ default should have the GCM suites first
382    assertEquals(cipherSuites[0], "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256");
383  }
384
385  @Test
386  public void testGetDefaultCipherSuitesJava10() {
387    String[] cipherSuites = X509Util.getDefaultCipherSuitesForJavaVersion("10");
388    // Java 9+ default should have the GCM suites first
389    assertEquals(cipherSuites[0], "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256");
390  }
391
392  @Test
393  public void testGetDefaultCipherSuitesJava11() {
394    String[] cipherSuites = X509Util.getDefaultCipherSuitesForJavaVersion("11");
395    // Java 11+ default should have the TLSv1.3 suites first
396    assertThat(cipherSuites[0], containsString("TLS_AES_128_GCM"));
397  }
398
399  @Test
400  public void testGetDefaultCipherSuitesUnknownVersion() {
401    String[] cipherSuites = X509Util.getDefaultCipherSuitesForJavaVersion("notaversion");
402    // If version can't be parsed, use the more conservative Java 8 default
403    assertThat(cipherSuites[0], containsString("CBC"));
404  }
405
406  @Test
407  public void testGetDefaultCipherSuitesNullVersion() {
408    assertThrows(NullPointerException.class, () -> {
409      X509Util.getDefaultCipherSuitesForJavaVersion(null);
410    });
411  }
412}