Skip to content

Commit 27684d0

Browse files
authored
HBASE-28777 mTLS client hostname verification doesn't work with OptionalSslHandler (#6149)
Signed-off-by: Balazs Meszaros <[email protected]>
1 parent d2a1f19 commit 27684d0

File tree

6 files changed

+289
-22
lines changed

6 files changed

+289
-22
lines changed

hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/HBaseTrustManager.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,13 @@ public void checkServerTrusted(X509Certificate[] chain, String authType, Socket
9292
public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
9393
throws CertificateException {
9494
x509ExtendedTrustManager.checkClientTrusted(chain, authType, engine);
95-
if (hostnameVerificationEnabled) {
95+
if (hostnameVerificationEnabled && engine != null) {
9696
try {
97+
if (engine.getPeerHost() == null) {
98+
LOG.warn(
99+
"Cannot perform client hostname verification, because peer information is not available");
100+
return;
101+
}
97102
performHostVerification(InetAddress.getByName(engine.getPeerHost()), chain[0]);
98103
} catch (UnknownHostException e) {
99104
throw new CertificateException("Failed to verify host", e);

hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/TestHBaseTrustManager.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.apache.hadoop.hbase.io.crypto.tls;
1919

2020
import static org.junit.Assert.assertThrows;
21+
import static org.mockito.Mockito.doReturn;
2122
import static org.mockito.Mockito.mock;
2223
import static org.mockito.Mockito.times;
2324
import static org.mockito.Mockito.verify;
@@ -36,6 +37,7 @@
3637
import java.util.Date;
3738
import java.util.List;
3839
import java.util.Random;
40+
import javax.net.ssl.SSLEngine;
3941
import javax.net.ssl.X509ExtendedTrustManager;
4042
import org.apache.hadoop.hbase.HBaseClassTestRule;
4143
import org.apache.hadoop.hbase.testclassification.MiscTests;
@@ -86,6 +88,8 @@ public class TestHBaseTrustManager {
8688
private InetAddress mockInetAddressWithHostname;
8789
private Socket mockSocketWithoutHostname;
8890
private Socket mockSocketWithHostname;
91+
private SSLEngine mockSSLEngineWithoutPeerhost;
92+
private SSLEngine mockSSLEngineWithPeerhost;
8993

9094
@BeforeClass
9195
public static void createKeyPair() throws Exception {
@@ -126,6 +130,12 @@ public void setup() throws Exception {
126130
mockSocketWithHostname = mock(Socket.class);
127131
when(mockSocketWithHostname.getInetAddress())
128132
.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();
129139
}
130140

131141
@SuppressWarnings("JavaUtilDate")
@@ -352,4 +362,35 @@ public void testClientTrustedWithHostnameVerificationEnabledWithHostnameNoRevers
352362
mockSocketWithHostname);
353363
}
354364

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+
}
355396
}

hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/X509TestContext.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -461,12 +461,13 @@ public X509TestContext cloneWithNewKeystoreCert(X509Certificate cert) {
461461
}
462462

463463
public void regenerateStores(X509KeyType keyStoreKeyType, X509KeyType trustStoreKeyType,
464-
KeyStoreFileType keyStoreFileType, KeyStoreFileType trustStoreFileType)
464+
KeyStoreFileType keyStoreFileType, KeyStoreFileType trustStoreFileType,
465+
String... subjectAltNames)
465466
throws GeneralSecurityException, IOException, OperatorCreationException {
466467

467468
trustStoreKeyPair = X509TestHelpers.generateKeyPair(trustStoreKeyType);
468469
keyStoreKeyPair = X509TestHelpers.generateKeyPair(keyStoreKeyType);
469-
createCertificates();
470+
createCertificates(subjectAltNames);
470471

471472
switch (keyStoreFileType) {
472473
case JKS:
@@ -499,7 +500,7 @@ public void regenerateStores(X509KeyType keyStoreKeyType, X509KeyType trustStore
499500
}
500501
}
501502

502-
private void createCertificates()
503+
private void createCertificates(String... subjectAltNames)
503504
throws GeneralSecurityException, IOException, OperatorCreationException {
504505
X500NameBuilder caNameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
505506
caNameBuilder.addRDN(BCStyle.CN,
@@ -510,7 +511,7 @@ private void createCertificates()
510511
X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
511512
nameBuilder.addRDN(BCStyle.CN,
512513
MethodHandles.lookup().lookupClass().getCanonicalName() + " Zookeeper Test");
513-
keyStoreCertificate = newCert(nameBuilder.build());
514+
keyStoreCertificate = newCert(nameBuilder.build(), subjectAltNames);
514515
}
515516

516517
/**

hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/NettyRpcServer.java

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import static org.apache.hadoop.hbase.io.crypto.tls.X509Util.HBASE_SERVER_NETTY_TLS_ENABLED;
2222
import static org.apache.hadoop.hbase.io.crypto.tls.X509Util.HBASE_SERVER_NETTY_TLS_SUPPORTPLAINTEXT;
2323
import static org.apache.hadoop.hbase.io.crypto.tls.X509Util.HBASE_SERVER_NETTY_TLS_WRAP_SIZE;
24-
import static org.apache.hadoop.hbase.io.crypto.tls.X509Util.TLS_CONFIG_REVERSE_DNS_LOOKUP_ENABLED;
2524

2625
import java.io.IOException;
2726
import java.io.InterruptedIOException;
@@ -386,30 +385,33 @@ private void initSSL(ChannelPipeline p, NettyServerRpcConnection conn, boolean s
386385
throws X509Exception, IOException {
387386
SslContext nettySslContext = getSslContext();
388387

388+
/*
389+
* our HostnameVerifier gets the host name from SSLEngine, so we have to construct the engine
390+
* properly by passing the remote address
391+
*/
392+
389393
if (supportPlaintext) {
390-
p.addLast("ssl", new OptionalSslHandler(nettySslContext));
394+
SocketAddress remoteAddress = p.channel().remoteAddress();
395+
OptionalSslHandler optionalSslHandler;
396+
397+
if (remoteAddress instanceof InetSocketAddress) {
398+
InetSocketAddress remoteInetAddress = (InetSocketAddress) remoteAddress;
399+
optionalSslHandler = new OptionalSslHandlerWithHostPort(nettySslContext,
400+
remoteInetAddress.getHostString(), remoteInetAddress.getPort());
401+
} else {
402+
optionalSslHandler = new OptionalSslHandler(nettySslContext);
403+
}
404+
405+
p.addLast("ssl", optionalSslHandler);
391406
LOG.debug("Dual mode SSL handler added for channel: {}", p.channel());
392407
} else {
393408
SocketAddress remoteAddress = p.channel().remoteAddress();
394409
SslHandler sslHandler;
395410

396411
if (remoteAddress instanceof InetSocketAddress) {
397412
InetSocketAddress remoteInetAddress = (InetSocketAddress) remoteAddress;
398-
String host;
399-
400-
if (conf.getBoolean(TLS_CONFIG_REVERSE_DNS_LOOKUP_ENABLED, true)) {
401-
host = remoteInetAddress.getHostName();
402-
} else {
403-
host = remoteInetAddress.getHostString();
404-
}
405-
406-
int port = remoteInetAddress.getPort();
407-
408-
/*
409-
* our HostnameVerifier gets the host name from SSLEngine, so we have to construct the
410-
* engine properly by passing the remote address
411-
*/
412-
sslHandler = nettySslContext.newHandler(p.channel().alloc(), host, port);
413+
sslHandler = nettySslContext.newHandler(p.channel().alloc(),
414+
remoteInetAddress.getHostString(), remoteInetAddress.getPort());
413415
} else {
414416
sslHandler = nettySslContext.newHandler(p.channel().alloc());
415417
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.hbase.ipc;
19+
20+
import org.apache.hbase.thirdparty.io.netty.channel.ChannelHandlerContext;
21+
import org.apache.hbase.thirdparty.io.netty.handler.ssl.OptionalSslHandler;
22+
import org.apache.hbase.thirdparty.io.netty.handler.ssl.SslContext;
23+
import org.apache.hbase.thirdparty.io.netty.handler.ssl.SslHandler;
24+
25+
class OptionalSslHandlerWithHostPort extends OptionalSslHandler {
26+
27+
private final String host;
28+
private final int port;
29+
30+
public OptionalSslHandlerWithHostPort(SslContext sslContext, String host, int port) {
31+
super(sslContext);
32+
this.host = host;
33+
this.port = port;
34+
}
35+
36+
@Override
37+
protected SslHandler newSslHandler(ChannelHandlerContext context, SslContext sslContext) {
38+
return sslContext.newHandler(context.alloc(), host, port);
39+
}
40+
}

0 commit comments

Comments
 (0)