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.Assert.assertThrows; 021import static org.mockito.Mockito.doReturn; 022import static org.mockito.Mockito.mock; 023import static org.mockito.Mockito.times; 024import static org.mockito.Mockito.verify; 025import static org.mockito.Mockito.when; 026 027import java.math.BigInteger; 028import java.net.InetAddress; 029import java.net.Socket; 030import java.security.KeyPair; 031import java.security.KeyPairGenerator; 032import java.security.Security; 033import java.security.cert.CertificateException; 034import java.security.cert.X509Certificate; 035import java.util.ArrayList; 036import java.util.Calendar; 037import java.util.Date; 038import java.util.List; 039import java.util.Random; 040import javax.net.ssl.SSLEngine; 041import javax.net.ssl.X509ExtendedTrustManager; 042import org.apache.hadoop.hbase.HBaseClassTestRule; 043import org.apache.hadoop.hbase.testclassification.MiscTests; 044import org.apache.hadoop.hbase.testclassification.SmallTests; 045import org.bouncycastle.asn1.x500.X500NameBuilder; 046import org.bouncycastle.asn1.x500.style.BCStyle; 047import org.bouncycastle.asn1.x509.BasicConstraints; 048import org.bouncycastle.asn1.x509.Extension; 049import org.bouncycastle.asn1.x509.GeneralName; 050import org.bouncycastle.asn1.x509.GeneralNames; 051import org.bouncycastle.asn1.x509.KeyUsage; 052import org.bouncycastle.cert.X509v3CertificateBuilder; 053import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; 054import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; 055import org.bouncycastle.jce.provider.BouncyCastleProvider; 056import org.bouncycastle.operator.ContentSigner; 057import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; 058import org.junit.AfterClass; 059import org.junit.Before; 060import org.junit.BeforeClass; 061import org.junit.ClassRule; 062import org.junit.Test; 063import org.junit.experimental.categories.Category; 064import org.mockito.stubbing.Answer; 065 066// 067/** 068 * Test cases taken and adapted from Apache ZooKeeper Project. We can only test calls to 069 * HBaseTrustManager using Sockets (not SSLEngines). This can be fine since the logic is the same. 070 * @see <a href= 071 * "https://github.com/apache/zookeeper/blob/c74658d398cdc1d207aa296cb6e20de00faec03e/zookeeper-server/src/test/java/org/apache/zookeeper/common/HBaseTrustManagerTest.java">Base 072 * revision</a> 073 */ 074@Category({ MiscTests.class, SmallTests.class }) 075public class TestHBaseTrustManager { 076 077 @ClassRule 078 public static final HBaseClassTestRule CLASS_RULE = 079 HBaseClassTestRule.forClass(TestHBaseTrustManager.class); 080 081 private static KeyPair keyPair; 082 083 private X509ExtendedTrustManager mockX509ExtendedTrustManager; 084 private static final String IP_ADDRESS = "127.0.0.1"; 085 private static final String HOSTNAME = "localhost"; 086 087 private InetAddress mockInetAddressWithoutHostname; 088 private InetAddress mockInetAddressWithHostname; 089 private Socket mockSocketWithoutHostname; 090 private Socket mockSocketWithHostname; 091 private SSLEngine mockSSLEngineWithoutPeerhost; 092 private SSLEngine mockSSLEngineWithPeerhost; 093 094 @BeforeClass 095 public static void createKeyPair() throws Exception { 096 Security.addProvider(new BouncyCastleProvider()); 097 KeyPairGenerator keyPairGenerator = 098 KeyPairGenerator.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME); 099 keyPairGenerator.initialize(4096); 100 keyPair = keyPairGenerator.genKeyPair(); 101 } 102 103 @AfterClass 104 public static void removeBouncyCastleProvider() throws Exception { 105 Security.removeProvider("BC"); 106 } 107 108 @Before 109 public void setup() throws Exception { 110 mockX509ExtendedTrustManager = mock(X509ExtendedTrustManager.class); 111 112 mockInetAddressWithoutHostname = mock(InetAddress.class); 113 when(mockInetAddressWithoutHostname.getHostAddress()) 114 .thenAnswer((Answer<?>) invocationOnMock -> IP_ADDRESS); 115 when(mockInetAddressWithoutHostname.toString()) 116 .thenAnswer((Answer<?>) invocationOnMock -> "/" + IP_ADDRESS); 117 118 mockInetAddressWithHostname = mock(InetAddress.class); 119 when(mockInetAddressWithHostname.getHostAddress()) 120 .thenAnswer((Answer<?>) invocationOnMock -> IP_ADDRESS); 121 when(mockInetAddressWithHostname.getHostName()) 122 .thenAnswer((Answer<?>) invocationOnMock -> HOSTNAME); 123 when(mockInetAddressWithHostname.toString()) 124 .thenAnswer((Answer<?>) invocationOnMock -> HOSTNAME + "/" + IP_ADDRESS); 125 126 mockSocketWithoutHostname = mock(Socket.class); 127 when(mockSocketWithoutHostname.getInetAddress()) 128 .thenAnswer((Answer<?>) invocationOnMock -> mockInetAddressWithoutHostname); 129 130 mockSocketWithHostname = mock(Socket.class); 131 when(mockSocketWithHostname.getInetAddress()) 132 .thenAnswer((Answer<?>) invocationOnMock -> mockInetAddressWithHostname); 133 134 mockSSLEngineWithoutPeerhost = mock(SSLEngine.class); 135 doReturn(null).when(mockSSLEngineWithoutPeerhost).getPeerHost(); 136 137 mockSSLEngineWithPeerhost = mock(SSLEngine.class); 138 doReturn(IP_ADDRESS).when(mockSSLEngineWithPeerhost).getPeerHost(); 139 } 140 141 @SuppressWarnings("JavaUtilDate") 142 private X509Certificate[] createSelfSignedCertificateChain(String ipAddress, String hostname) 143 throws Exception { 144 X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE); 145 nameBuilder.addRDN(BCStyle.CN, "NOT_LOCALHOST"); 146 Date notBefore = new Date(); 147 Calendar cal = Calendar.getInstance(); 148 cal.setTime(notBefore); 149 cal.add(Calendar.YEAR, 1); 150 Date notAfter = cal.getTime(); 151 BigInteger serialNumber = new BigInteger(128, new Random()); 152 153 X509v3CertificateBuilder certificateBuilder = 154 new JcaX509v3CertificateBuilder(nameBuilder.build(), serialNumber, notBefore, notAfter, 155 nameBuilder.build(), keyPair.getPublic()) 156 .addExtension(Extension.basicConstraints, true, new BasicConstraints(0)) 157 .addExtension(Extension.keyUsage, true, 158 new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyCertSign | KeyUsage.cRLSign)); 159 160 List<GeneralName> generalNames = new ArrayList<>(); 161 if (ipAddress != null) { 162 generalNames.add(new GeneralName(GeneralName.iPAddress, ipAddress)); 163 } 164 if (hostname != null) { 165 generalNames.add(new GeneralName(GeneralName.dNSName, hostname)); 166 } 167 168 if (!generalNames.isEmpty()) { 169 certificateBuilder.addExtension(Extension.subjectAlternativeName, true, 170 new GeneralNames(generalNames.toArray(new GeneralName[] {}))); 171 } 172 173 ContentSigner contentSigner = 174 new JcaContentSignerBuilder("SHA256WithRSAEncryption").build(keyPair.getPrivate()); 175 176 return new X509Certificate[] { 177 new JcaX509CertificateConverter().getCertificate(certificateBuilder.build(contentSigner)) }; 178 } 179 180 @Test 181 public void testServerHostnameVerificationWithHostnameVerificationDisabled() throws Exception { 182 HBaseTrustManager trustManager = 183 new HBaseTrustManager(mockX509ExtendedTrustManager, false, false); 184 185 X509Certificate[] certificateChain = createSelfSignedCertificateChain(IP_ADDRESS, HOSTNAME); 186 trustManager.checkServerTrusted(certificateChain, null, mockSocketWithHostname); 187 188 verify(mockInetAddressWithHostname, times(0)).getHostAddress(); 189 verify(mockInetAddressWithHostname, times(0)).getHostName(); 190 191 verify(mockX509ExtendedTrustManager, times(1)).checkServerTrusted(certificateChain, null, 192 mockSocketWithHostname); 193 } 194 195 @SuppressWarnings("checkstyle:linelength") 196 @Test 197 public void testServerTrustedWithHostnameVerificationDisabled() throws Exception { 198 HBaseTrustManager trustManager = 199 new HBaseTrustManager(mockX509ExtendedTrustManager, false, false); 200 201 X509Certificate[] certificateChain = createSelfSignedCertificateChain(IP_ADDRESS, HOSTNAME); 202 trustManager.checkServerTrusted(certificateChain, null, mockSocketWithHostname); 203 204 verify(mockInetAddressWithHostname, times(0)).getHostAddress(); 205 verify(mockInetAddressWithHostname, times(0)).getHostName(); 206 207 verify(mockX509ExtendedTrustManager, times(1)).checkServerTrusted(certificateChain, null, 208 mockSocketWithHostname); 209 } 210 211 @Test 212 public void testServerTrustedWithHostnameVerificationEnabled() throws Exception { 213 HBaseTrustManager trustManager = 214 new HBaseTrustManager(mockX509ExtendedTrustManager, true, true); 215 216 X509Certificate[] certificateChain = createSelfSignedCertificateChain(null, HOSTNAME); 217 trustManager.checkServerTrusted(certificateChain, null, mockSocketWithHostname); 218 219 verify(mockInetAddressWithHostname, times(1)).getHostAddress(); 220 verify(mockInetAddressWithHostname, times(1)).getHostName(); 221 222 verify(mockX509ExtendedTrustManager, times(1)).checkServerTrusted(certificateChain, null, 223 mockSocketWithHostname); 224 } 225 226 @Test 227 public void testServerTrustedWithHostnameVerificationEnabledUsingIpAddress() throws Exception { 228 HBaseTrustManager trustManager = 229 new HBaseTrustManager(mockX509ExtendedTrustManager, true, true); 230 231 X509Certificate[] certificateChain = createSelfSignedCertificateChain(IP_ADDRESS, null); 232 trustManager.checkServerTrusted(certificateChain, null, mockSocketWithHostname); 233 234 verify(mockInetAddressWithHostname, times(1)).getHostAddress(); 235 verify(mockInetAddressWithHostname, times(0)).getHostName(); 236 237 verify(mockX509ExtendedTrustManager, times(1)).checkServerTrusted(certificateChain, null, 238 mockSocketWithHostname); 239 } 240 241 @Test 242 public void testServerTrustedWithHostnameVerificationEnabledNoReverseLookup() throws Exception { 243 HBaseTrustManager trustManager = 244 new HBaseTrustManager(mockX509ExtendedTrustManager, true, false); 245 246 X509Certificate[] certificateChain = createSelfSignedCertificateChain(null, HOSTNAME); 247 248 // We only include hostname in the cert above, but the socket passed in is for an ip address. 249 // This mismatch would succeed if reverse lookup is enabled, but here fails since it's 250 // not enabled. 251 assertThrows(CertificateException.class, 252 () -> trustManager.checkServerTrusted(certificateChain, null, mockSocketWithoutHostname)); 253 254 verify(mockInetAddressWithoutHostname, times(1)).getHostAddress(); 255 verify(mockInetAddressWithoutHostname, times(0)).getHostName(); 256 257 verify(mockX509ExtendedTrustManager, times(1)).checkServerTrusted(certificateChain, null, 258 mockSocketWithoutHostname); 259 } 260 261 @Test 262 public void testServerTrustedWithHostnameVerificationEnabledWithHostnameNoReverseLookup() 263 throws Exception { 264 HBaseTrustManager trustManager = 265 new HBaseTrustManager(mockX509ExtendedTrustManager, true, false); 266 267 X509Certificate[] certificateChain = createSelfSignedCertificateChain(null, HOSTNAME); 268 269 // since the socket inetAddress already has a hostname, we don't need reverse lookup. 270 // so this succeeds 271 trustManager.checkServerTrusted(certificateChain, null, mockSocketWithHostname); 272 273 verify(mockInetAddressWithHostname, times(1)).getHostAddress(); 274 verify(mockInetAddressWithHostname, times(1)).getHostName(); 275 276 verify(mockX509ExtendedTrustManager, times(1)).checkServerTrusted(certificateChain, null, 277 mockSocketWithHostname); 278 } 279 280 @Test 281 public void testClientTrustedWithHostnameVerificationDisabled() throws Exception { 282 HBaseTrustManager trustManager = 283 new HBaseTrustManager(mockX509ExtendedTrustManager, false, false); 284 285 X509Certificate[] certificateChain = createSelfSignedCertificateChain(null, HOSTNAME); 286 trustManager.checkClientTrusted(certificateChain, null, mockSocketWithHostname); 287 288 verify(mockInetAddressWithHostname, times(0)).getHostAddress(); 289 verify(mockInetAddressWithHostname, times(0)).getHostName(); 290 291 verify(mockX509ExtendedTrustManager, times(1)).checkClientTrusted(certificateChain, null, 292 mockSocketWithHostname); 293 } 294 295 @Test 296 public void testClientTrustedWithHostnameVerificationEnabled() throws Exception { 297 HBaseTrustManager trustManager = 298 new HBaseTrustManager(mockX509ExtendedTrustManager, true, true); 299 300 X509Certificate[] certificateChain = createSelfSignedCertificateChain(null, HOSTNAME); 301 trustManager.checkClientTrusted(certificateChain, null, mockSocketWithHostname); 302 303 verify(mockInetAddressWithHostname, times(1)).getHostAddress(); 304 verify(mockInetAddressWithHostname, times(1)).getHostName(); 305 306 verify(mockX509ExtendedTrustManager, times(1)).checkClientTrusted(certificateChain, null, 307 mockSocketWithHostname); 308 } 309 310 @Test 311 public void testClientTrustedWithHostnameVerificationEnabledUsingIpAddress() throws Exception { 312 HBaseTrustManager trustManager = 313 new HBaseTrustManager(mockX509ExtendedTrustManager, true, true); 314 315 X509Certificate[] certificateChain = createSelfSignedCertificateChain(IP_ADDRESS, null); 316 trustManager.checkClientTrusted(certificateChain, null, mockSocketWithHostname); 317 318 verify(mockInetAddressWithHostname, times(1)).getHostAddress(); 319 verify(mockInetAddressWithHostname, times(0)).getHostName(); 320 321 verify(mockX509ExtendedTrustManager, times(1)).checkClientTrusted(certificateChain, null, 322 mockSocketWithHostname); 323 } 324 325 @Test 326 public void testClientTrustedWithHostnameVerificationEnabledWithoutReverseLookup() 327 throws Exception { 328 HBaseTrustManager trustManager = 329 new HBaseTrustManager(mockX509ExtendedTrustManager, true, false); 330 331 X509Certificate[] certificateChain = createSelfSignedCertificateChain(null, HOSTNAME); 332 333 // We only include hostname in the cert above, but the socket passed in is for an ip address. 334 // This mismatch would succeed if reverse lookup is enabled, but here fails since it's 335 // not enabled. 336 assertThrows(CertificateException.class, 337 () -> trustManager.checkClientTrusted(certificateChain, null, mockSocketWithoutHostname)); 338 339 verify(mockInetAddressWithoutHostname, times(1)).getHostAddress(); 340 verify(mockInetAddressWithoutHostname, times(0)).getHostName(); 341 342 verify(mockX509ExtendedTrustManager, times(1)).checkClientTrusted(certificateChain, null, 343 mockSocketWithoutHostname); 344 } 345 346 @Test 347 public void testClientTrustedWithHostnameVerificationEnabledWithHostnameNoReverseLookup() 348 throws Exception { 349 HBaseTrustManager trustManager = 350 new HBaseTrustManager(mockX509ExtendedTrustManager, true, false); 351 352 X509Certificate[] certificateChain = createSelfSignedCertificateChain(null, HOSTNAME); 353 354 // since the socket inetAddress already has a hostname, we don't need reverse lookup. 355 // so this succeeds 356 trustManager.checkClientTrusted(certificateChain, null, mockSocketWithHostname); 357 358 verify(mockInetAddressWithHostname, times(1)).getHostAddress(); 359 verify(mockInetAddressWithHostname, times(1)).getHostName(); 360 361 verify(mockX509ExtendedTrustManager, times(1)).checkClientTrusted(certificateChain, null, 362 mockSocketWithHostname); 363 } 364 365 @Test 366 public void testClientTrustedSslEngineWithPeerHostReverseLookup() throws Exception { 367 HBaseTrustManager trustManager = 368 new HBaseTrustManager(mockX509ExtendedTrustManager, true, true); 369 X509Certificate[] certificateChain = createSelfSignedCertificateChain(null, HOSTNAME); 370 trustManager.checkClientTrusted(certificateChain, null, mockSSLEngineWithPeerhost); 371 } 372 373 @Test(expected = CertificateException.class) 374 public void testClientTrustedSslEngineWithPeerHostNoReverseLookup() throws Exception { 375 HBaseTrustManager trustManager = 376 new HBaseTrustManager(mockX509ExtendedTrustManager, true, false); 377 X509Certificate[] certificateChain = createSelfSignedCertificateChain(null, HOSTNAME); 378 trustManager.checkClientTrusted(certificateChain, null, mockSSLEngineWithPeerhost); 379 } 380 381 @Test 382 public void testClientTrustedSslEngineWithoutPeerHost() throws Exception { 383 HBaseTrustManager trustManager = 384 new HBaseTrustManager(mockX509ExtendedTrustManager, true, false); 385 X509Certificate[] certificateChain = createSelfSignedCertificateChain(null, HOSTNAME); 386 trustManager.checkClientTrusted(certificateChain, null, mockSSLEngineWithoutPeerhost); 387 } 388 389 @Test 390 public void testClientTrustedSslEngineNotAvailable() throws Exception { 391 HBaseTrustManager trustManager = 392 new HBaseTrustManager(mockX509ExtendedTrustManager, true, false); 393 X509Certificate[] certificateChain = createSelfSignedCertificateChain(null, HOSTNAME); 394 trustManager.checkClientTrusted(certificateChain, null, (SSLEngine) null); 395 } 396}