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