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