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.security; 019 020import static org.apache.hadoop.hbase.ipc.TestProtobufRpcServiceImpl.SERVICE; 021import static org.junit.Assert.assertThrows; 022import static org.junit.Assert.assertTrue; 023 024import java.io.File; 025import java.io.IOException; 026import java.lang.invoke.MethodHandles; 027import java.net.InetSocketAddress; 028import java.security.GeneralSecurityException; 029import java.security.Security; 030import java.security.cert.X509Certificate; 031import javax.net.ssl.SSLHandshakeException; 032import org.apache.commons.io.FileUtils; 033import org.apache.hadoop.conf.Configuration; 034import org.apache.hadoop.hbase.HBaseCommonTestingUtil; 035import org.apache.hadoop.hbase.io.crypto.tls.KeyStoreFileType; 036import org.apache.hadoop.hbase.io.crypto.tls.X509KeyType; 037import org.apache.hadoop.hbase.io.crypto.tls.X509TestContext; 038import org.apache.hadoop.hbase.io.crypto.tls.X509TestContextProvider; 039import org.apache.hadoop.hbase.io.crypto.tls.X509Util; 040import org.apache.hadoop.hbase.ipc.FifoRpcScheduler; 041import org.apache.hadoop.hbase.ipc.NettyRpcClient; 042import org.apache.hadoop.hbase.ipc.NettyRpcServer; 043import org.apache.hadoop.hbase.ipc.RpcClient; 044import org.apache.hadoop.hbase.ipc.RpcClientFactory; 045import org.apache.hadoop.hbase.ipc.RpcServer; 046import org.apache.hadoop.hbase.ipc.RpcServerFactory; 047import org.apache.hadoop.hbase.ipc.TestProtobufRpcServiceImpl; 048import org.bouncycastle.asn1.x500.X500NameBuilder; 049import org.bouncycastle.asn1.x500.style.BCStyle; 050import org.bouncycastle.jce.provider.BouncyCastleProvider; 051import org.bouncycastle.operator.OperatorCreationException; 052import org.junit.After; 053import org.junit.AfterClass; 054import org.junit.Before; 055import org.junit.BeforeClass; 056import org.junit.Test; 057import org.junit.runners.Parameterized; 058 059import org.apache.hbase.thirdparty.com.google.common.collect.Lists; 060import org.apache.hbase.thirdparty.com.google.common.io.Closeables; 061import org.apache.hbase.thirdparty.com.google.protobuf.ServiceException; 062 063import org.apache.hadoop.hbase.shaded.ipc.protobuf.generated.TestProtos; 064import org.apache.hadoop.hbase.shaded.ipc.protobuf.generated.TestRpcServiceProtos; 065 066public abstract class AbstractTestMutualTls { 067 protected static HBaseCommonTestingUtil UTIL; 068 069 protected static File DIR; 070 071 protected static X509TestContextProvider PROVIDER; 072 073 private X509TestContext x509TestContext; 074 075 protected RpcServer rpcServer; 076 077 protected RpcClient rpcClient; 078 private TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface stub; 079 080 @Parameterized.Parameter(0) 081 public X509KeyType caKeyType; 082 083 @Parameterized.Parameter(1) 084 public X509KeyType certKeyType; 085 086 @Parameterized.Parameter(2) 087 public String keyPassword; 088 @Parameterized.Parameter(3) 089 public boolean expectSuccess; 090 091 @Parameterized.Parameter(4) 092 public boolean validateHostnames; 093 094 @Parameterized.Parameter(5) 095 public CertConfig certConfig; 096 097 public enum CertConfig { 098 // For no cert, we literally pass no certificate to the server. It's possible (assuming server 099 // allows it based on ClientAuth mode) to use SSL without a KeyStore which will still do all 100 // the handshaking but without a client cert. This is what we do here. 101 // This mode only makes sense for client side, as server side must return a cert. 102 NO_CLIENT_CERT, 103 // For non-verifiable cert, we create a new certificate which is signed by a different 104 // CA. So we're passing a cert, but the client/server can't verify it. 105 NON_VERIFIABLE_CERT, 106 // Good cert is the default mode, which uses a cert signed by the same CA both sides 107 // and the hostname should match (localhost) 108 GOOD_CERT, 109 // For good cert/bad host, we create a new certificate signed by the same CA. But 110 // this cert has a SANS that will not match the localhost peer. 111 VERIFIABLE_CERT_WITH_BAD_HOST 112 } 113 114 @BeforeClass 115 public static void setUpBeforeClass() throws IOException { 116 UTIL = new HBaseCommonTestingUtil(); 117 Security.addProvider(new BouncyCastleProvider()); 118 DIR = 119 new File(UTIL.getDataTestDir(AbstractTestTlsRejectPlainText.class.getSimpleName()).toString()) 120 .getCanonicalFile(); 121 FileUtils.forceMkdir(DIR); 122 Configuration conf = UTIL.getConfiguration(); 123 conf.setClass(RpcClientFactory.CUSTOM_RPC_CLIENT_IMPL_CONF_KEY, NettyRpcClient.class, 124 RpcClient.class); 125 conf.setClass(RpcServerFactory.CUSTOM_RPC_SERVER_IMPL_CONF_KEY, NettyRpcServer.class, 126 RpcServer.class); 127 conf.setBoolean(X509Util.HBASE_SERVER_NETTY_TLS_ENABLED, true); 128 conf.setBoolean(X509Util.HBASE_SERVER_NETTY_TLS_SUPPORTPLAINTEXT, false); 129 conf.setBoolean(X509Util.HBASE_CLIENT_NETTY_TLS_ENABLED, true); 130 PROVIDER = new X509TestContextProvider(conf, DIR); 131 } 132 133 @AfterClass 134 public static void cleanUp() { 135 Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME); 136 UTIL.cleanupTestDir(); 137 } 138 139 protected abstract void initialize(Configuration serverConf, Configuration clientConf) 140 throws IOException, GeneralSecurityException, OperatorCreationException; 141 142 @Before 143 public void setUp() throws Exception { 144 x509TestContext = PROVIDER.get(caKeyType, certKeyType, keyPassword.toCharArray()); 145 x509TestContext.setConfigurations(KeyStoreFileType.JKS, KeyStoreFileType.JKS); 146 147 Configuration serverConf = new Configuration(UTIL.getConfiguration()); 148 Configuration clientConf = new Configuration(UTIL.getConfiguration()); 149 150 initialize(serverConf, clientConf); 151 152 rpcServer = new NettyRpcServer(null, "testRpcServer", 153 Lists.newArrayList(new RpcServer.BlockingServiceAndInterface(SERVICE, null)), 154 new InetSocketAddress("localhost", 0), serverConf, new FifoRpcScheduler(serverConf, 1), true); 155 rpcServer.start(); 156 157 rpcClient = new NettyRpcClient(clientConf); 158 stub = TestProtobufRpcServiceImpl.newBlockingStub(rpcClient, rpcServer.getListenerAddress()); 159 } 160 161 protected void handleCertConfig(Configuration confToSet) 162 throws GeneralSecurityException, IOException, OperatorCreationException { 163 switch (certConfig) { 164 case NO_CLIENT_CERT: 165 // clearing out the keystore location will cause no cert to be sent. 166 confToSet.set(X509Util.TLS_CONFIG_KEYSTORE_LOCATION, ""); 167 break; 168 case NON_VERIFIABLE_CERT: 169 // to simulate a bad cert, we inject a new keystore into the client side. 170 // the same truststore exists, so it will still successfully verify the server cert 171 // but since the new client keystore cert is created from a new CA (which the server doesn't 172 // have), 173 // the server will not be able to verify it. 174 X509TestContext context = 175 PROVIDER.get(caKeyType, certKeyType, "random value".toCharArray()); 176 context.setKeystoreConfigurations(KeyStoreFileType.JKS, confToSet); 177 break; 178 case VERIFIABLE_CERT_WITH_BAD_HOST: 179 // to simulate a good cert with a bad host, we need to create a new cert using the existing 180 // context's CA/truststore. Here we can pass any random SANS, as long as it won't match 181 // localhost or any reasonable name that this test might run on. 182 X509Certificate cert = x509TestContext.newCert(new X500NameBuilder(BCStyle.INSTANCE) 183 .addRDN(BCStyle.CN, 184 MethodHandles.lookup().lookupClass().getCanonicalName() + " With Bad Host Test") 185 .build(), "www.example.com"); 186 x509TestContext.cloneWithNewKeystoreCert(cert) 187 .setKeystoreConfigurations(KeyStoreFileType.JKS, confToSet); 188 break; 189 default: 190 break; 191 } 192 } 193 194 @After 195 public void tearDown() throws IOException { 196 if (rpcServer != null) { 197 rpcServer.stop(); 198 } 199 Closeables.close(rpcClient, true); 200 x509TestContext.clearConfigurations(); 201 x509TestContext.getConf().unset(X509Util.TLS_CONFIG_OCSP); 202 x509TestContext.getConf().unset(X509Util.TLS_CONFIG_CLR); 203 x509TestContext.getConf().unset(X509Util.TLS_CONFIG_PROTOCOL); 204 System.clearProperty("com.sun.net.ssl.checkRevocation"); 205 System.clearProperty("com.sun.security.enableCRLDP"); 206 Security.setProperty("ocsp.enable", Boolean.FALSE.toString()); 207 Security.setProperty("com.sun.security.enableCRLDP", Boolean.FALSE.toString()); 208 } 209 210 @Test 211 public void testClientAuth() throws Exception { 212 if (expectSuccess) { 213 // we expect no exception, so if one is thrown the test will fail 214 submitRequest(); 215 } else { 216 ServiceException se = assertThrows(ServiceException.class, this::submitRequest); 217 // The SSLHandshakeException is encapsulated differently depending on the TLS version 218 boolean seenSSLHandshakeException = false; 219 Throwable current = se; 220 do { 221 if (current instanceof SSLHandshakeException) { 222 seenSSLHandshakeException = true; 223 break; 224 } 225 current = current.getCause(); 226 } while (current != null); 227 assertTrue("Exception chain does not include SSLHandshakeException", 228 seenSSLHandshakeException); 229 } 230 } 231 232 private void submitRequest() throws ServiceException { 233 stub.echo(null, TestProtos.EchoRequestProto.newBuilder().setMessage("hello world").build()); 234 } 235}