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