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}