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.junit.jupiter.api.Assertions.assertArrayEquals;
021import static org.junit.jupiter.api.Assertions.assertEquals;
022import static org.junit.jupiter.api.Assertions.assertFalse;
023import static org.junit.jupiter.api.Assertions.assertThrows;
024import static org.junit.jupiter.api.Assertions.assertTrue;
025import static org.junit.jupiter.api.Assumptions.assumeTrue;
026import static org.mockito.Mockito.mock;
027
028import java.security.Security;
029import java.util.Arrays;
030import java.util.Collections;
031import org.apache.hadoop.hbase.HBaseParameterizedTestTemplate;
032import org.apache.hadoop.hbase.exceptions.KeyManagerException;
033import org.apache.hadoop.hbase.exceptions.SSLContextException;
034import org.apache.hadoop.hbase.exceptions.TrustManagerException;
035import org.apache.hadoop.hbase.testclassification.SecurityTests;
036import org.apache.hadoop.hbase.testclassification.SmallTests;
037import org.junit.jupiter.api.Tag;
038import org.junit.jupiter.api.TestTemplate;
039
040import org.apache.hbase.thirdparty.io.netty.buffer.ByteBufAllocator;
041import org.apache.hbase.thirdparty.io.netty.handler.ssl.SslContext;
042
043/**
044 * This file has been copied from the Apache ZooKeeper project.
045 * @see <a href=
046 *      "https://github.com/apache/zookeeper/blob/master/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509UtilTest.java">Base
047 *      revision</a>
048 */
049@Tag(SecurityTests.TAG)
050@Tag(SmallTests.TAG)
051@HBaseParameterizedTestTemplate(name = "{index}: caKeyType={0}, certKeyType={1}, keyPassword={2}")
052public class TestX509Util extends AbstractTestX509Parameterized {
053
054  private static final char[] EMPTY_CHAR_ARRAY = new char[0];
055
056  public TestX509Util(X509KeyType caKeyType, X509KeyType certKeyType, char[] keyPassword) {
057    super(caKeyType, certKeyType, keyPassword);
058  }
059
060  @TestTemplate
061  public void testCreateSSLContextWithClientAuthDefault() throws Exception {
062    SslContext sslContext = X509Util.createSslContextForServer(conf);
063    ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class);
064    assertTrue(sslContext.newEngine(byteBufAllocatorMock).getNeedClientAuth());
065  }
066
067  @TestTemplate
068  public void testCreateSSLContextWithClientAuthNEED() throws Exception {
069    conf.set(X509Util.HBASE_SERVER_NETTY_TLS_CLIENT_AUTH_MODE, X509Util.ClientAuth.NEED.name());
070    SslContext sslContext = X509Util.createSslContextForServer(conf);
071    ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class);
072    assertTrue(sslContext.newEngine(byteBufAllocatorMock).getNeedClientAuth());
073  }
074
075  @TestTemplate
076  public void testCreateSSLContextWithClientAuthWANT() throws Exception {
077    conf.set(X509Util.HBASE_SERVER_NETTY_TLS_CLIENT_AUTH_MODE, X509Util.ClientAuth.WANT.name());
078    SslContext sslContext = X509Util.createSslContextForServer(conf);
079    ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class);
080    assertTrue(sslContext.newEngine(byteBufAllocatorMock).getWantClientAuth());
081  }
082
083  @TestTemplate
084  public void testCreateSSLContextWithClientAuthNONE() throws Exception {
085    conf.set(X509Util.HBASE_SERVER_NETTY_TLS_CLIENT_AUTH_MODE, X509Util.ClientAuth.NONE.name());
086    SslContext sslContext = X509Util.createSslContextForServer(conf);
087    ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class);
088    assertFalse(sslContext.newEngine(byteBufAllocatorMock).getNeedClientAuth());
089    assertFalse(sslContext.newEngine(byteBufAllocatorMock).getWantClientAuth());
090  }
091
092  @TestTemplate
093  public void testCreateSSLContextWithoutCustomProtocol() throws Exception {
094    SslContext sslContext = X509Util.createSslContextForClient(conf);
095    ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class);
096    assertArrayEquals(new String[] { "TLSv1.3", "TLSv1.2" },
097      sslContext.newEngine(byteBufAllocatorMock).getEnabledProtocols());
098  }
099
100  @TestTemplate
101  public void testCreateTcNativeSSLContextWithoutCustomProtocol() throws Exception {
102    conf.set(X509Util.TLS_USE_OPENSSL, "true");
103    SslContext sslContext = X509Util.createSslContextForClient(conf);
104    ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class);
105    assertArrayEquals(new String[] { "TLSv1.3", "TLSv1.2" },
106      sslContext.newEngine(byteBufAllocatorMock).getEnabledProtocols());
107  }
108
109  @TestTemplate
110  public void testCreateSSLContextWithCustomProtocol() throws Exception {
111    final String protocol = "TLSv1.1";
112    conf.set(X509Util.TLS_CONFIG_PROTOCOL, protocol);
113    ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class);
114    SslContext sslContext = X509Util.createSslContextForServer(conf);
115    assertEquals(Collections.singletonList(protocol),
116      Arrays.asList(sslContext.newEngine(byteBufAllocatorMock).getEnabledProtocols()));
117  }
118
119  @TestTemplate
120  public void testCreateSSLContextWithoutKeyStoreLocationServer() {
121    conf.unset(X509Util.TLS_CONFIG_KEYSTORE_LOCATION);
122    assertThrows(SSLContextException.class, () -> {
123      X509Util.createSslContextForServer(conf);
124    });
125  }
126
127  @TestTemplate
128  public void testCreateSSLContextWithoutKeyStoreLocationClient() throws Exception {
129    conf.unset(X509Util.TLS_CONFIG_KEYSTORE_LOCATION);
130    X509Util.createSslContextForClient(conf);
131  }
132
133  @TestTemplate
134  public void testCreateSSLContextWithoutTrustStoreLocationClient() throws Exception {
135    conf.unset(X509Util.TLS_CONFIG_TRUSTSTORE_LOCATION);
136    X509Util.createSslContextForClient(conf);
137  }
138
139  @TestTemplate
140  public void testCreateSSLContextWithoutTrustStoreLocationServer() throws Exception {
141    conf.unset(X509Util.TLS_CONFIG_TRUSTSTORE_LOCATION);
142    X509Util.createSslContextForServer(conf);
143  }
144
145  // It would be great to test the value of PKIXBuilderParameters#setRevocationEnabled,
146  // but it does not appear to be possible
147  @TestTemplate
148  public void testCRLEnabled() throws Exception {
149    conf.setBoolean(X509Util.TLS_CONFIG_CLR, true);
150    X509Util.createSslContextForServer(conf);
151    assertTrue(Boolean.valueOf(System.getProperty("com.sun.net.ssl.checkRevocation")));
152    assertTrue(Boolean.valueOf(System.getProperty("com.sun.security.enableCRLDP")));
153    assertFalse(Boolean.valueOf(Security.getProperty("ocsp.enable")));
154  }
155
156  @TestTemplate
157  public void testCRLDisabled() throws Exception {
158    X509Util.createSslContextForServer(conf);
159    assertFalse(Boolean.valueOf(System.getProperty("com.sun.net.ssl.checkRevocation")));
160    assertFalse(Boolean.valueOf(System.getProperty("com.sun.security.enableCRLDP")));
161    assertFalse(Boolean.valueOf(Security.getProperty("ocsp.enable")));
162  }
163
164  @TestTemplate
165  public void testLoadPEMKeyStore() throws Exception {
166    // Make sure we can instantiate a key manager from the PEM file on disk
167    X509Util.createKeyManager(
168      x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
169      x509TestContext.getKeyStorePassword(), KeyStoreFileType.PEM.getPropertyValue());
170  }
171
172  @TestTemplate
173  public void testLoadPEMKeyStoreNullPassword() throws Exception {
174    assumeTrue(Arrays.equals(x509TestContext.getKeyStorePassword(), EMPTY_CHAR_ARRAY));
175    // Make sure that empty password and null password are treated the same
176    X509Util.createKeyManager(
177      x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(), null,
178      KeyStoreFileType.PEM.getPropertyValue());
179  }
180
181  @TestTemplate
182  public void testLoadPEMKeyStoreAutodetectStoreFileType() throws Exception {
183    // Make sure we can instantiate a key manager from the PEM file on disk
184    X509Util.createKeyManager(
185      x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
186      x509TestContext.getKeyStorePassword(),
187      null /* null StoreFileType means 'autodetect from file extension' */);
188  }
189
190  @TestTemplate
191  public void testLoadPEMKeyStoreWithWrongPassword() {
192    assertThrows(KeyManagerException.class, () -> {
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
201  @TestTemplate
202  public void testLoadPEMTrustStore() throws Exception {
203    // Make sure we can instantiate a trust manager from the PEM file on disk
204    X509Util.createTrustManager(
205      x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
206      x509TestContext.getTrustStorePassword(), KeyStoreFileType.PEM.getPropertyValue(), false,
207      false, true, true);
208  }
209
210  @TestTemplate
211  public void testLoadPEMTrustStoreNullPassword() throws Exception {
212    assumeTrue(Arrays.equals(x509TestContext.getTrustStorePassword(), EMPTY_CHAR_ARRAY));
213    // Make sure that empty password and null password are treated the same
214    X509Util.createTrustManager(
215      x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM).getAbsolutePath(), null,
216      KeyStoreFileType.PEM.getPropertyValue(), false, false, true, true);
217  }
218
219  @TestTemplate
220  public void testLoadPEMTrustStoreAutodetectStoreFileType() throws Exception {
221    // Make sure we can instantiate a trust manager from the PEM file on disk
222    X509Util.createTrustManager(
223      x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
224      x509TestContext.getTrustStorePassword(), null, // null StoreFileType means 'autodetect from
225                                                     // file extension'
226      false, false, true, true);
227  }
228
229  @TestTemplate
230  public void testLoadJKSKeyStore() throws Exception {
231    // Make sure we can instantiate a key manager from the JKS file on disk
232    X509Util.createKeyManager(
233      x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
234      x509TestContext.getKeyStorePassword(), KeyStoreFileType.JKS.getPropertyValue());
235  }
236
237  @TestTemplate
238  public void testLoadJKSKeyStoreNullPassword() throws Exception {
239    assumeTrue(Arrays.equals(x509TestContext.getKeyStorePassword(), EMPTY_CHAR_ARRAY));
240    // Make sure that empty password and null password are treated the same
241    X509Util.createKeyManager(
242      x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(), null,
243      KeyStoreFileType.JKS.getPropertyValue());
244  }
245
246  @TestTemplate
247  public void testLoadJKSKeyStoreAutodetectStoreFileType() throws Exception {
248    // Make sure we can instantiate a key manager from the JKS file on disk
249    X509Util.createKeyManager(
250      x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
251      x509TestContext.getKeyStorePassword(),
252      null /* null StoreFileType means 'autodetect from file extension' */);
253  }
254
255  @TestTemplate
256  public void testLoadJKSKeyStoreWithWrongPassword() {
257    assertThrows(KeyManagerException.class, () -> {
258      // Attempting to load with the wrong key password should fail
259      X509Util.createKeyManager(
260        x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
261        "wrong password".toCharArray(), KeyStoreFileType.JKS.getPropertyValue());
262    });
263  }
264
265  @TestTemplate
266  public void testLoadJKSTrustStore() throws Exception {
267    // Make sure we can instantiate a trust manager from the JKS file on disk
268    X509Util.createTrustManager(
269      x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
270      x509TestContext.getTrustStorePassword(), KeyStoreFileType.JKS.getPropertyValue(), true, true,
271      true, true);
272  }
273
274  @TestTemplate
275  public void testLoadJKSTrustStoreNullPassword() throws Exception {
276    assumeTrue(Arrays.equals(x509TestContext.getTrustStorePassword(), EMPTY_CHAR_ARRAY));
277    // Make sure that empty password and null password are treated the same
278    X509Util.createTrustManager(
279      x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(), null,
280      KeyStoreFileType.JKS.getPropertyValue(), false, false, true, true);
281  }
282
283  @TestTemplate
284  public void testLoadJKSTrustStoreAutodetectStoreFileType() throws Exception {
285    // Make sure we can instantiate a trust manager from the JKS file on disk
286    // null StoreFileType means 'autodetect from file extension'
287    X509Util.createTrustManager(
288      x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
289      x509TestContext.getTrustStorePassword(), null, true, true, true, true);
290  }
291
292  @TestTemplate
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  @TestTemplate
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  @TestTemplate
312  public void testLoadPKCS12KeyStoreNullPassword() throws Exception {
313    assumeTrue(Arrays.equals(x509TestContext.getKeyStorePassword(), 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  @TestTemplate
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  @TestTemplate
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  @TestTemplate
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  @TestTemplate
349  public void testLoadPKCS12TrustStoreNullPassword() throws Exception {
350    assumeTrue(Arrays.equals(x509TestContext.getTrustStorePassword(), 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  @TestTemplate
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  @TestTemplate
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}