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}