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