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.Matchers.equalTo;
021import static org.junit.Assert.assertArrayEquals;
022import static org.junit.Assert.assertEquals;
023import static org.junit.Assert.assertFalse;
024import static org.junit.Assert.assertThrows;
025import static org.junit.Assert.assertTrue;
026import static org.junit.Assume.assumeThat;
027import static org.mockito.Mockito.mock;
028
029import java.security.Security;
030import java.util.Arrays;
031import java.util.Collections;
032import org.apache.hadoop.hbase.HBaseClassTestRule;
033import org.apache.hadoop.hbase.exceptions.KeyManagerException;
034import org.apache.hadoop.hbase.exceptions.SSLContextException;
035import org.apache.hadoop.hbase.exceptions.TrustManagerException;
036import org.apache.hadoop.hbase.testclassification.SecurityTests;
037import org.apache.hadoop.hbase.testclassification.SmallTests;
038import org.junit.ClassRule;
039import org.junit.Test;
040import org.junit.experimental.categories.Category;
041import org.junit.runner.RunWith;
042import org.junit.runners.Parameterized;
043
044import org.apache.hbase.thirdparty.io.netty.buffer.ByteBufAllocator;
045import org.apache.hbase.thirdparty.io.netty.handler.ssl.SslContext;
046
047/**
048 * This file has been copied from the Apache ZooKeeper project.
049 * @see <a href=
050 *      "https://github.com/apache/zookeeper/blob/master/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509UtilTest.java">Base
051 *      revision</a>
052 */
053@RunWith(Parameterized.class)
054@Category({ SecurityTests.class, SmallTests.class })
055public class TestX509Util extends AbstractTestX509Parameterized {
056
057  @ClassRule
058  public static final HBaseClassTestRule CLASS_RULE =
059    HBaseClassTestRule.forClass(TestX509Util.class);
060
061  private static final char[] EMPTY_CHAR_ARRAY = new char[0];
062
063  @Test
064  public void testCreateSSLContextWithClientAuthDefault() throws Exception {
065    SslContext sslContext = X509Util.createSslContextForServer(conf);
066    ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class);
067    assertTrue(sslContext.newEngine(byteBufAllocatorMock).getNeedClientAuth());
068  }
069
070  @Test
071  public void testCreateSSLContextWithClientAuthNEED() throws Exception {
072    conf.set(X509Util.HBASE_SERVER_NETTY_TLS_CLIENT_AUTH_MODE, X509Util.ClientAuth.NEED.name());
073    SslContext sslContext = X509Util.createSslContextForServer(conf);
074    ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class);
075    assertTrue(sslContext.newEngine(byteBufAllocatorMock).getNeedClientAuth());
076  }
077
078  @Test
079  public void testCreateSSLContextWithClientAuthWANT() throws Exception {
080    conf.set(X509Util.HBASE_SERVER_NETTY_TLS_CLIENT_AUTH_MODE, X509Util.ClientAuth.WANT.name());
081    SslContext sslContext = X509Util.createSslContextForServer(conf);
082    ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class);
083    assertTrue(sslContext.newEngine(byteBufAllocatorMock).getWantClientAuth());
084  }
085
086  @Test
087  public void testCreateSSLContextWithClientAuthNONE() throws Exception {
088    conf.set(X509Util.HBASE_SERVER_NETTY_TLS_CLIENT_AUTH_MODE, X509Util.ClientAuth.NONE.name());
089    SslContext sslContext = X509Util.createSslContextForServer(conf);
090    ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class);
091    assertFalse(sslContext.newEngine(byteBufAllocatorMock).getNeedClientAuth());
092    assertFalse(sslContext.newEngine(byteBufAllocatorMock).getWantClientAuth());
093  }
094
095  @Test
096  public void testCreateSSLContextWithoutCustomProtocol() throws Exception {
097    SslContext sslContext = X509Util.createSslContextForClient(conf);
098    ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class);
099    assertArrayEquals(new String[] { "TLSv1.3", "TLSv1.2" },
100      sslContext.newEngine(byteBufAllocatorMock).getEnabledProtocols());
101  }
102
103  @Test
104  public void testCreateTcNativeSSLContextWithoutCustomProtocol() throws Exception {
105    conf.set(X509Util.TLS_USE_OPENSSL, "true");
106    SslContext sslContext = X509Util.createSslContextForClient(conf);
107    ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class);
108    assertArrayEquals(new String[] { "TLSv1.3", "TLSv1.2" },
109      sslContext.newEngine(byteBufAllocatorMock).getEnabledProtocols());
110  }
111
112  @Test
113  public void testCreateSSLContextWithCustomProtocol() throws Exception {
114    final String protocol = "TLSv1.1";
115    conf.set(X509Util.TLS_CONFIG_PROTOCOL, protocol);
116    ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class);
117    SslContext sslContext = X509Util.createSslContextForServer(conf);
118    assertEquals(Collections.singletonList(protocol),
119      Arrays.asList(sslContext.newEngine(byteBufAllocatorMock).getEnabledProtocols()));
120  }
121
122  @Test(expected = SSLContextException.class)
123  public void testCreateSSLContextWithoutKeyStoreLocationServer() throws Exception {
124    conf.unset(X509Util.TLS_CONFIG_KEYSTORE_LOCATION);
125    X509Util.createSslContextForServer(conf);
126  }
127
128  @Test
129  public void testCreateSSLContextWithoutKeyStoreLocationClient() throws Exception {
130    conf.unset(X509Util.TLS_CONFIG_KEYSTORE_LOCATION);
131    X509Util.createSslContextForClient(conf);
132  }
133
134  @Test
135  public void testCreateSSLContextWithoutTrustStoreLocationClient() throws Exception {
136    conf.unset(X509Util.TLS_CONFIG_TRUSTSTORE_LOCATION);
137    X509Util.createSslContextForClient(conf);
138  }
139
140  @Test
141  public void testCreateSSLContextWithoutTrustStoreLocationServer() throws Exception {
142    conf.unset(X509Util.TLS_CONFIG_TRUSTSTORE_LOCATION);
143    X509Util.createSslContextForServer(conf);
144  }
145
146  // It would be great to test the value of PKIXBuilderParameters#setRevocationEnabled,
147  // but it does not appear to be possible
148  @Test
149  public void testCRLEnabled() throws Exception {
150    conf.setBoolean(X509Util.TLS_CONFIG_CLR, true);
151    X509Util.createSslContextForServer(conf);
152    assertTrue(Boolean.valueOf(System.getProperty("com.sun.net.ssl.checkRevocation")));
153    assertTrue(Boolean.valueOf(System.getProperty("com.sun.security.enableCRLDP")));
154    assertFalse(Boolean.valueOf(Security.getProperty("ocsp.enable")));
155  }
156
157  @Test
158  public void testCRLDisabled() throws Exception {
159    X509Util.createSslContextForServer(conf);
160    assertFalse(Boolean.valueOf(System.getProperty("com.sun.net.ssl.checkRevocation")));
161    assertFalse(Boolean.valueOf(System.getProperty("com.sun.security.enableCRLDP")));
162    assertFalse(Boolean.valueOf(Security.getProperty("ocsp.enable")));
163  }
164
165  @Test
166  public void testLoadPEMKeyStore() throws Exception {
167    // Make sure we can instantiate a key manager from the PEM file on disk
168    X509Util.createKeyManager(
169      x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
170      x509TestContext.getKeyStorePassword(), KeyStoreFileType.PEM.getPropertyValue());
171  }
172
173  @Test
174  public void testLoadPEMKeyStoreNullPassword() throws Exception {
175    assumeThat(x509TestContext.getKeyStorePassword(), equalTo(EMPTY_CHAR_ARRAY));
176    // Make sure that empty password and null password are treated the same
177    X509Util.createKeyManager(
178      x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(), null,
179      KeyStoreFileType.PEM.getPropertyValue());
180  }
181
182  @Test
183  public void testLoadPEMKeyStoreAutodetectStoreFileType() throws Exception {
184    // Make sure we can instantiate a key manager from the PEM file on disk
185    X509Util.createKeyManager(
186      x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
187      x509TestContext.getKeyStorePassword(),
188      null /* null StoreFileType means 'autodetect from file extension' */);
189  }
190
191  @Test(expected = KeyManagerException.class)
192  public void testLoadPEMKeyStoreWithWrongPassword() throws Exception {
193    // Attempting to load with the wrong key password should fail
194    X509Util.createKeyManager(
195      x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
196      "wrong password".toCharArray(), // intentionally use the wrong password
197      KeyStoreFileType.PEM.getPropertyValue());
198  }
199
200  @Test
201  public void testLoadPEMTrustStore() throws Exception {
202    // Make sure we can instantiate a trust manager from the PEM file on disk
203    X509Util.createTrustManager(
204      x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
205      x509TestContext.getTrustStorePassword(), KeyStoreFileType.PEM.getPropertyValue(), false,
206      false, true, true);
207  }
208
209  @Test
210  public void testLoadPEMTrustStoreNullPassword() throws Exception {
211    assumeThat(x509TestContext.getTrustStorePassword(), equalTo(EMPTY_CHAR_ARRAY));
212    // Make sure that empty password and null password are treated the same
213    X509Util.createTrustManager(
214      x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM).getAbsolutePath(), null,
215      KeyStoreFileType.PEM.getPropertyValue(), false, false, true, true);
216  }
217
218  @Test
219  public void testLoadPEMTrustStoreAutodetectStoreFileType() throws Exception {
220    // Make sure we can instantiate a trust manager from the PEM file on disk
221    X509Util.createTrustManager(
222      x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
223      x509TestContext.getTrustStorePassword(), null, // null StoreFileType means 'autodetect from
224                                                     // file extension'
225      false, false, true, true);
226  }
227
228  @Test
229  public void testLoadJKSKeyStore() throws Exception {
230    // Make sure we can instantiate a key manager from the JKS file on disk
231    X509Util.createKeyManager(
232      x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
233      x509TestContext.getKeyStorePassword(), KeyStoreFileType.JKS.getPropertyValue());
234  }
235
236  @Test
237  public void testLoadJKSKeyStoreNullPassword() throws Exception {
238    assumeThat(x509TestContext.getKeyStorePassword(), equalTo(EMPTY_CHAR_ARRAY));
239    // Make sure that empty password and null password are treated the same
240    X509Util.createKeyManager(
241      x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(), null,
242      KeyStoreFileType.JKS.getPropertyValue());
243  }
244
245  @Test
246  public void testLoadJKSKeyStoreAutodetectStoreFileType() throws Exception {
247    // Make sure we can instantiate a key manager from the JKS file on disk
248    X509Util.createKeyManager(
249      x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
250      x509TestContext.getKeyStorePassword(),
251      null /* null StoreFileType means 'autodetect from file extension' */);
252  }
253
254  @Test
255  public void testLoadJKSKeyStoreWithWrongPassword() {
256    assertThrows(KeyManagerException.class, () -> {
257      // Attempting to load with the wrong key password should fail
258      X509Util.createKeyManager(
259        x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
260        "wrong password".toCharArray(), KeyStoreFileType.JKS.getPropertyValue());
261    });
262  }
263
264  @Test
265  public void testLoadJKSTrustStore() throws Exception {
266    // Make sure we can instantiate a trust manager from the JKS file on disk
267    X509Util.createTrustManager(
268      x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
269      x509TestContext.getTrustStorePassword(), KeyStoreFileType.JKS.getPropertyValue(), true, true,
270      true, true);
271  }
272
273  @Test
274  public void testLoadJKSTrustStoreNullPassword() throws Exception {
275    assumeThat(x509TestContext.getTrustStorePassword(), equalTo(EMPTY_CHAR_ARRAY));
276    // Make sure that empty password and null password are treated the same
277    X509Util.createTrustManager(
278      x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(), null,
279      KeyStoreFileType.JKS.getPropertyValue(), false, false, true, true);
280  }
281
282  @Test
283  public void testLoadJKSTrustStoreAutodetectStoreFileType() throws Exception {
284    // Make sure we can instantiate a trust manager from the JKS file on disk
285    X509Util.createTrustManager(
286      x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
287      x509TestContext.getTrustStorePassword(), null, // null StoreFileType means 'autodetect from
288                                                     // file extension'
289      true, true, true, true);
290  }
291
292  @Test
293  public void testLoadJKSTrustStoreWithWrongPassword() {
294    assertThrows(TrustManagerException.class, () -> {
295      // Attempting to load with the wrong key password should fail
296      X509Util.createTrustManager(
297        x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
298        "wrong password".toCharArray(), KeyStoreFileType.JKS.getPropertyValue(), true, true, true,
299        true);
300    });
301  }
302
303  @Test
304  public void testLoadPKCS12KeyStore() throws Exception {
305    // Make sure we can instantiate a key manager from the PKCS12 file on disk
306    X509Util.createKeyManager(
307      x509TestContext.getKeyStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(),
308      x509TestContext.getKeyStorePassword(), KeyStoreFileType.PKCS12.getPropertyValue());
309  }
310
311  @Test
312  public void testLoadPKCS12KeyStoreNullPassword() throws Exception {
313    assumeThat(x509TestContext.getKeyStorePassword(), equalTo(EMPTY_CHAR_ARRAY));
314    // Make sure that empty password and null password are treated the same
315    X509Util.createKeyManager(
316      x509TestContext.getKeyStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(), null,
317      KeyStoreFileType.PKCS12.getPropertyValue());
318  }
319
320  @Test
321  public void testLoadPKCS12KeyStoreAutodetectStoreFileType() throws Exception {
322    // Make sure we can instantiate a key manager from the PKCS12 file on disk
323    X509Util.createKeyManager(
324      x509TestContext.getKeyStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(),
325      x509TestContext.getKeyStorePassword(),
326      null /* null StoreFileType means 'autodetect from file extension' */);
327  }
328
329  @Test
330  public void testLoadPKCS12KeyStoreWithWrongPassword() {
331    assertThrows(KeyManagerException.class, () -> {
332      // Attempting to load with the wrong key password should fail
333      X509Util.createKeyManager(
334        x509TestContext.getKeyStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(),
335        "wrong password".toCharArray(), KeyStoreFileType.PKCS12.getPropertyValue());
336    });
337  }
338
339  @Test
340  public void testLoadPKCS12TrustStore() throws Exception {
341    // Make sure we can instantiate a trust manager from the PKCS12 file on disk
342    X509Util.createTrustManager(
343      x509TestContext.getTrustStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(),
344      x509TestContext.getTrustStorePassword(), KeyStoreFileType.PKCS12.getPropertyValue(), true,
345      true, true, true);
346  }
347
348  @Test
349  public void testLoadPKCS12TrustStoreNullPassword() throws Exception {
350    assumeThat(x509TestContext.getTrustStorePassword(), equalTo(EMPTY_CHAR_ARRAY));
351    // Make sure that empty password and null password are treated the same
352    X509Util.createTrustManager(
353      x509TestContext.getTrustStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(), null,
354      KeyStoreFileType.PKCS12.getPropertyValue(), false, false, true, true);
355  }
356
357  @Test
358  public void testLoadPKCS12TrustStoreAutodetectStoreFileType() throws Exception {
359    // Make sure we can instantiate a trust manager from the PKCS12 file on disk
360    X509Util.createTrustManager(
361      x509TestContext.getTrustStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(),
362      x509TestContext.getTrustStorePassword(), null, // null StoreFileType means 'autodetect from
363                                                     // file extension'
364      true, true, true, true);
365  }
366
367  @Test
368  public void testLoadPKCS12TrustStoreWithWrongPassword() {
369    assertThrows(TrustManagerException.class, () -> {
370      // Attempting to load with the wrong key password should fail
371      X509Util.createTrustManager(
372        x509TestContext.getTrustStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(),
373        "wrong password".toCharArray(), KeyStoreFileType.PKCS12.getPropertyValue(), true, true,
374        true, true);
375    });
376  }
377
378}