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.assertArrayEquals; 021import static org.junit.jupiter.api.Assertions.assertEquals; 022import static org.junit.jupiter.api.Assertions.assertFalse; 023import static org.junit.jupiter.api.Assertions.assertNull; 024import static org.junit.jupiter.api.Assertions.assertThrows; 025import static org.junit.jupiter.api.Assertions.assertTrue; 026import static org.junit.jupiter.api.Assumptions.assumeTrue; 027import static org.mockito.Mockito.mock; 028 029import java.security.Security; 030import java.util.Arrays; 031import java.util.Collections; 032import org.apache.hadoop.conf.Configuration; 033import org.apache.hadoop.hbase.HBaseParameterizedTestTemplate; 034import org.apache.hadoop.hbase.exceptions.KeyManagerException; 035import org.apache.hadoop.hbase.exceptions.SSLContextException; 036import org.apache.hadoop.hbase.exceptions.TrustManagerException; 037import org.apache.hadoop.hbase.testclassification.SecurityTests; 038import org.apache.hadoop.hbase.testclassification.SmallTests; 039import org.junit.jupiter.api.Tag; 040import org.junit.jupiter.api.TestTemplate; 041 042import org.apache.hbase.thirdparty.io.netty.buffer.ByteBufAllocator; 043import org.apache.hbase.thirdparty.io.netty.handler.ssl.SslContext; 044 045/** 046 * This file has been copied from the Apache ZooKeeper project. 047 * @see <a href= 048 * "https://github.com/apache/zookeeper/blob/master/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509UtilTest.java">Base 049 * revision</a> 050 */ 051@Tag(SecurityTests.TAG) 052@Tag(SmallTests.TAG) 053@HBaseParameterizedTestTemplate(name = "{index}: caKeyType={0}, certKeyType={1}, keyPassword={2}") 054public class TestX509Util extends AbstractTestX509Parameterized { 055 056 private static final char[] EMPTY_CHAR_ARRAY = new char[0]; 057 058 public TestX509Util(X509KeyType caKeyType, X509KeyType certKeyType, char[] keyPassword) { 059 super(caKeyType, certKeyType, keyPassword); 060 } 061 062 @TestTemplate 063 public void testCreateSSLContextWithClientAuthDefault() throws Exception { 064 SslContext sslContext = X509Util.createSslContextForServer(conf); 065 ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class); 066 assertTrue(sslContext.newEngine(byteBufAllocatorMock).getNeedClientAuth()); 067 } 068 069 @TestTemplate 070 public void testCreateSSLContextWithClientAuthNEED() throws Exception { 071 conf.set(X509Util.HBASE_SERVER_NETTY_TLS_CLIENT_AUTH_MODE, X509Util.ClientAuth.NEED.name()); 072 SslContext sslContext = X509Util.createSslContextForServer(conf); 073 ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class); 074 assertTrue(sslContext.newEngine(byteBufAllocatorMock).getNeedClientAuth()); 075 } 076 077 @TestTemplate 078 public void testCreateSSLContextWithClientAuthWANT() throws Exception { 079 conf.set(X509Util.HBASE_SERVER_NETTY_TLS_CLIENT_AUTH_MODE, X509Util.ClientAuth.WANT.name()); 080 SslContext sslContext = X509Util.createSslContextForServer(conf); 081 ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class); 082 assertTrue(sslContext.newEngine(byteBufAllocatorMock).getWantClientAuth()); 083 } 084 085 @TestTemplate 086 public void testCreateSSLContextWithClientAuthNONE() throws Exception { 087 conf.set(X509Util.HBASE_SERVER_NETTY_TLS_CLIENT_AUTH_MODE, X509Util.ClientAuth.NONE.name()); 088 SslContext sslContext = X509Util.createSslContextForServer(conf); 089 ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class); 090 assertFalse(sslContext.newEngine(byteBufAllocatorMock).getNeedClientAuth()); 091 assertFalse(sslContext.newEngine(byteBufAllocatorMock).getWantClientAuth()); 092 } 093 094 @TestTemplate 095 public void testCreateSSLContextWithoutCustomProtocol() throws Exception { 096 SslContext sslContext = X509Util.createSslContextForClient(conf); 097 ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class); 098 assertArrayEquals(new String[] { "TLSv1.3", "TLSv1.2" }, 099 sslContext.newEngine(byteBufAllocatorMock).getEnabledProtocols()); 100 } 101 102 @TestTemplate 103 public void testCreateTcNativeSSLContextWithoutCustomProtocol() throws Exception { 104 conf.set(X509Util.TLS_USE_OPENSSL, "true"); 105 SslContext sslContext = X509Util.createSslContextForClient(conf); 106 ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class); 107 assertArrayEquals(new String[] { "TLSv1.3", "TLSv1.2" }, 108 sslContext.newEngine(byteBufAllocatorMock).getEnabledProtocols()); 109 } 110 111 @TestTemplate 112 public void testCreateSSLContextWithCustomProtocol() throws Exception { 113 final String protocol = "TLSv1.1"; 114 conf.set(X509Util.TLS_CONFIG_PROTOCOL, protocol); 115 ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class); 116 SslContext sslContext = X509Util.createSslContextForServer(conf); 117 assertEquals(Collections.singletonList(protocol), 118 Arrays.asList(sslContext.newEngine(byteBufAllocatorMock).getEnabledProtocols())); 119 } 120 121 @TestTemplate 122 public void testCreateSSLContextWithoutKeyStoreLocationServer() { 123 conf.unset(X509Util.TLS_CONFIG_KEYSTORE_LOCATION); 124 assertThrows(SSLContextException.class, () -> { 125 X509Util.createSslContextForServer(conf); 126 }); 127 } 128 129 @TestTemplate 130 public void testCreateSSLContextWithoutKeyStoreLocationClient() throws Exception { 131 conf.unset(X509Util.TLS_CONFIG_KEYSTORE_LOCATION); 132 X509Util.createSslContextForClient(conf); 133 } 134 135 @TestTemplate 136 public void testCreateSSLContextWithoutTrustStoreLocationClient() throws Exception { 137 conf.unset(X509Util.TLS_CONFIG_TRUSTSTORE_LOCATION); 138 X509Util.createSslContextForClient(conf); 139 } 140 141 @TestTemplate 142 public void testCreateSSLContextWithoutTrustStoreLocationServer() throws Exception { 143 conf.unset(X509Util.TLS_CONFIG_TRUSTSTORE_LOCATION); 144 X509Util.createSslContextForServer(conf); 145 } 146 147 // It would be great to test the value of PKIXBuilderParameters#setRevocationEnabled, 148 // but it does not appear to be possible 149 @TestTemplate 150 public void testCRLEnabled() throws Exception { 151 conf.setBoolean(X509Util.TLS_CONFIG_CLR, true); 152 X509Util.createSslContextForServer(conf); 153 assertTrue(Boolean.valueOf(System.getProperty("com.sun.net.ssl.checkRevocation"))); 154 assertTrue(Boolean.valueOf(System.getProperty("com.sun.security.enableCRLDP"))); 155 assertFalse(Boolean.valueOf(Security.getProperty("ocsp.enable"))); 156 } 157 158 @TestTemplate 159 public void testCRLDisabled() throws Exception { 160 X509Util.createSslContextForServer(conf); 161 assertFalse(Boolean.valueOf(System.getProperty("com.sun.net.ssl.checkRevocation"))); 162 assertFalse(Boolean.valueOf(System.getProperty("com.sun.security.enableCRLDP"))); 163 assertFalse(Boolean.valueOf(Security.getProperty("ocsp.enable"))); 164 } 165 166 @TestTemplate 167 public void testLoadPEMKeyStore() throws Exception { 168 // Make sure we can instantiate a key manager from the PEM file on disk 169 X509Util.createKeyManager( 170 x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(), 171 x509TestContext.getKeyStorePassword(), KeyStoreFileType.PEM.getPropertyValue()); 172 } 173 174 @TestTemplate 175 public void testLoadPEMKeyStoreNullPassword() throws Exception { 176 assumeTrue(Arrays.equals(x509TestContext.getKeyStorePassword(), EMPTY_CHAR_ARRAY)); 177 // Make sure that empty password and null password are treated the same 178 X509Util.createKeyManager( 179 x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(), null, 180 KeyStoreFileType.PEM.getPropertyValue()); 181 } 182 183 @TestTemplate 184 public void testLoadPEMKeyStoreAutodetectStoreFileType() throws Exception { 185 // Make sure we can instantiate a key manager from the PEM file on disk 186 X509Util.createKeyManager( 187 x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(), 188 x509TestContext.getKeyStorePassword(), 189 null /* null StoreFileType means 'autodetect from file extension' */); 190 } 191 192 @TestTemplate 193 public void testLoadPEMKeyStoreWithWrongPassword() { 194 assertThrows(KeyManagerException.class, () -> { 195 // Attempting to load with the wrong key password should fail 196 X509Util.createKeyManager( 197 x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(), 198 "wrong password".toCharArray(), // intentionally use the wrong password 199 KeyStoreFileType.PEM.getPropertyValue()); 200 }); 201 } 202 203 @TestTemplate 204 public void testLoadPEMTrustStore() throws Exception { 205 // Make sure we can instantiate a trust manager from the PEM file on disk 206 X509Util.createTrustManager( 207 x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM).getAbsolutePath(), 208 x509TestContext.getTrustStorePassword(), KeyStoreFileType.PEM.getPropertyValue(), false, 209 false, true, true); 210 } 211 212 @TestTemplate 213 public void testLoadPEMTrustStoreNullPassword() throws Exception { 214 assumeTrue(Arrays.equals(x509TestContext.getTrustStorePassword(), EMPTY_CHAR_ARRAY)); 215 // Make sure that empty password and null password are treated the same 216 X509Util.createTrustManager( 217 x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM).getAbsolutePath(), null, 218 KeyStoreFileType.PEM.getPropertyValue(), false, false, true, true); 219 } 220 221 @TestTemplate 222 public void testLoadPEMTrustStoreAutodetectStoreFileType() throws Exception { 223 // Make sure we can instantiate a trust manager from the PEM file on disk 224 X509Util.createTrustManager( 225 x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM).getAbsolutePath(), 226 x509TestContext.getTrustStorePassword(), null, // null StoreFileType means 'autodetect from 227 // file extension' 228 false, false, true, true); 229 } 230 231 @TestTemplate 232 public void testLoadJKSKeyStore() throws Exception { 233 // Make sure we can instantiate a key manager from the JKS file on disk 234 X509Util.createKeyManager( 235 x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(), 236 x509TestContext.getKeyStorePassword(), KeyStoreFileType.JKS.getPropertyValue()); 237 } 238 239 @TestTemplate 240 public void testLoadJKSKeyStoreNullPassword() throws Exception { 241 assumeTrue(Arrays.equals(x509TestContext.getKeyStorePassword(), EMPTY_CHAR_ARRAY)); 242 // Make sure that empty password and null password are treated the same 243 X509Util.createKeyManager( 244 x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(), null, 245 KeyStoreFileType.JKS.getPropertyValue()); 246 } 247 248 @TestTemplate 249 public void testLoadJKSKeyStoreAutodetectStoreFileType() throws Exception { 250 // Make sure we can instantiate a key manager from the JKS file on disk 251 X509Util.createKeyManager( 252 x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(), 253 x509TestContext.getKeyStorePassword(), 254 null /* null StoreFileType means 'autodetect from file extension' */); 255 } 256 257 @TestTemplate 258 public void testLoadJKSKeyStoreWithWrongPassword() { 259 assertThrows(KeyManagerException.class, () -> { 260 // Attempting to load with the wrong key password should fail 261 X509Util.createKeyManager( 262 x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(), 263 "wrong password".toCharArray(), KeyStoreFileType.JKS.getPropertyValue()); 264 }); 265 } 266 267 @TestTemplate 268 public void testLoadJKSTrustStore() throws Exception { 269 // Make sure we can instantiate a trust manager from the JKS file on disk 270 X509Util.createTrustManager( 271 x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(), 272 x509TestContext.getTrustStorePassword(), KeyStoreFileType.JKS.getPropertyValue(), true, true, 273 true, true); 274 } 275 276 @TestTemplate 277 public void testLoadJKSTrustStoreNullPassword() throws Exception { 278 assumeTrue(Arrays.equals(x509TestContext.getTrustStorePassword(), EMPTY_CHAR_ARRAY)); 279 // Make sure that empty password and null password are treated the same 280 X509Util.createTrustManager( 281 x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(), null, 282 KeyStoreFileType.JKS.getPropertyValue(), false, false, true, true); 283 } 284 285 @TestTemplate 286 public void testLoadJKSTrustStoreAutodetectStoreFileType() throws Exception { 287 // Make sure we can instantiate a trust manager from the JKS file on disk 288 // null StoreFileType means 'autodetect from file extension' 289 X509Util.createTrustManager( 290 x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(), 291 x509TestContext.getTrustStorePassword(), null, true, true, true, true); 292 } 293 294 @TestTemplate 295 public void testLoadJKSTrustStoreWithWrongPassword() { 296 assertThrows(TrustManagerException.class, () -> { 297 // Attempting to load with the wrong key password should fail 298 X509Util.createTrustManager( 299 x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(), 300 "wrong password".toCharArray(), KeyStoreFileType.JKS.getPropertyValue(), true, true, true, 301 true); 302 }); 303 } 304 305 @TestTemplate 306 public void testLoadPKCS12KeyStore() throws Exception { 307 // Make sure we can instantiate a key manager from the PKCS12 file on disk 308 X509Util.createKeyManager( 309 x509TestContext.getKeyStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(), 310 x509TestContext.getKeyStorePassword(), KeyStoreFileType.PKCS12.getPropertyValue()); 311 } 312 313 @TestTemplate 314 public void testLoadPKCS12KeyStoreNullPassword() throws Exception { 315 assumeTrue(Arrays.equals(x509TestContext.getKeyStorePassword(), EMPTY_CHAR_ARRAY)); 316 // Make sure that empty password and null password are treated the same 317 X509Util.createKeyManager( 318 x509TestContext.getKeyStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(), null, 319 KeyStoreFileType.PKCS12.getPropertyValue()); 320 } 321 322 @TestTemplate 323 public void testLoadPKCS12KeyStoreAutodetectStoreFileType() throws Exception { 324 // Make sure we can instantiate a key manager from the PKCS12 file on disk 325 X509Util.createKeyManager( 326 x509TestContext.getKeyStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(), 327 x509TestContext.getKeyStorePassword(), 328 null /* null StoreFileType means 'autodetect from file extension' */); 329 } 330 331 @TestTemplate 332 public void testLoadPKCS12KeyStoreWithWrongPassword() { 333 assertThrows(KeyManagerException.class, () -> { 334 // Attempting to load with the wrong key password should fail 335 X509Util.createKeyManager( 336 x509TestContext.getKeyStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(), 337 "wrong password".toCharArray(), KeyStoreFileType.PKCS12.getPropertyValue()); 338 }); 339 } 340 341 @TestTemplate 342 public void testLoadPKCS12TrustStore() throws Exception { 343 // Make sure we can instantiate a trust manager from the PKCS12 file on disk 344 X509Util.createTrustManager( 345 x509TestContext.getTrustStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(), 346 x509TestContext.getTrustStorePassword(), KeyStoreFileType.PKCS12.getPropertyValue(), true, 347 true, true, true); 348 } 349 350 @TestTemplate 351 public void testLoadPKCS12TrustStoreNullPassword() throws Exception { 352 assumeTrue(Arrays.equals(x509TestContext.getTrustStorePassword(), EMPTY_CHAR_ARRAY)); 353 // Make sure that empty password and null password are treated the same 354 X509Util.createTrustManager( 355 x509TestContext.getTrustStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(), null, 356 KeyStoreFileType.PKCS12.getPropertyValue(), false, false, true, true); 357 } 358 359 @TestTemplate 360 public void testLoadPKCS12TrustStoreAutodetectStoreFileType() throws Exception { 361 // Make sure we can instantiate a trust manager from the PKCS12 file on disk 362 X509Util.createTrustManager( 363 x509TestContext.getTrustStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(), 364 x509TestContext.getTrustStorePassword(), null, // null StoreFileType means 'autodetect from 365 // file extension' 366 true, true, true, true); 367 } 368 369 @TestTemplate 370 public void testLoadPKCS12TrustStoreWithWrongPassword() { 371 assertThrows(TrustManagerException.class, () -> { 372 // Attempting to load with the wrong key password should fail 373 X509Util.createTrustManager( 374 x509TestContext.getTrustStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(), 375 "wrong password".toCharArray(), KeyStoreFileType.PKCS12.getPropertyValue(), true, true, 376 true, true); 377 }); 378 } 379 380 // --------------------------------------------------------------------------- 381 // Role-scoped configuration resolution (single-EKU certificate support) 382 // --------------------------------------------------------------------------- 383 384 @TestTemplate 385 public void testCreateSSLContextForClientUsesRoleScopedKeystoreWhenSet() throws Exception { 386 // Move the legacy keystore values to the client-scoped keys and clear the legacy keys, so 387 // that a successful context build proves the client-scoped keys were consulted. 388 String location = conf.get(X509Util.TLS_CONFIG_KEYSTORE_LOCATION); 389 String password = conf.get(X509Util.TLS_CONFIG_KEYSTORE_PASSWORD); 390 String type = conf.get(X509Util.TLS_CONFIG_KEYSTORE_TYPE); 391 conf.set(X509Util.TLS_CONFIG_CLIENT_KEYSTORE_LOCATION, location); 392 conf.set(X509Util.TLS_CONFIG_CLIENT_KEYSTORE_PASSWORD, password); 393 conf.set(X509Util.TLS_CONFIG_CLIENT_KEYSTORE_TYPE, type); 394 conf.unset(X509Util.TLS_CONFIG_KEYSTORE_LOCATION); 395 conf.unset(X509Util.TLS_CONFIG_KEYSTORE_PASSWORD); 396 conf.unset(X509Util.TLS_CONFIG_KEYSTORE_TYPE); 397 398 SslContext sslContext = X509Util.createSslContextForClient(conf); 399 ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class); 400 // Handshake would fail if the key manager weren't wired; smoke-test that engine creation works. 401 assertTrue( 402 sslContext.newEngine(byteBufAllocatorMock).getSSLParameters().getProtocols().length > 0); 403 } 404 405 @TestTemplate 406 public void testCreateSSLContextForServerUsesRoleScopedKeystoreWhenSet() throws Exception { 407 String location = conf.get(X509Util.TLS_CONFIG_KEYSTORE_LOCATION); 408 String password = conf.get(X509Util.TLS_CONFIG_KEYSTORE_PASSWORD); 409 String type = conf.get(X509Util.TLS_CONFIG_KEYSTORE_TYPE); 410 conf.set(X509Util.TLS_CONFIG_SERVER_KEYSTORE_LOCATION, location); 411 conf.set(X509Util.TLS_CONFIG_SERVER_KEYSTORE_PASSWORD, password); 412 conf.set(X509Util.TLS_CONFIG_SERVER_KEYSTORE_TYPE, type); 413 conf.unset(X509Util.TLS_CONFIG_KEYSTORE_LOCATION); 414 conf.unset(X509Util.TLS_CONFIG_KEYSTORE_PASSWORD); 415 conf.unset(X509Util.TLS_CONFIG_KEYSTORE_TYPE); 416 417 SslContext sslContext = X509Util.createSslContextForServer(conf); 418 ByteBufAllocator byteBufAllocatorMock = mock(ByteBufAllocator.class); 419 assertTrue( 420 sslContext.newEngine(byteBufAllocatorMock).getSSLParameters().getProtocols().length > 0); 421 } 422 423 @TestTemplate 424 public void testCreateSSLContextForServerThrowsWhenNeitherKeystoreSet() { 425 conf.unset(X509Util.TLS_CONFIG_KEYSTORE_LOCATION); 426 conf.unset(X509Util.TLS_CONFIG_SERVER_KEYSTORE_LOCATION); 427 SSLContextException ex = 428 assertThrows(SSLContextException.class, () -> X509Util.createSslContextForServer(conf)); 429 // The error should name both keys so the operator knows what to set. 430 assertTrue(ex.getMessage().contains(X509Util.TLS_CONFIG_SERVER_KEYSTORE_LOCATION), 431 "message should mention role-scoped key, got: " + ex.getMessage()); 432 assertTrue(ex.getMessage().contains(X509Util.TLS_CONFIG_KEYSTORE_LOCATION), 433 "message should mention legacy key, got: " + ex.getMessage()); 434 } 435 436 @TestTemplate 437 public void testValidateClientAuthTrustStoreRejectsNeedWithoutTrustStore() { 438 IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, 439 () -> X509Util.validateClientAuthTrustStore(X509Util.ClientAuth.NEED, null, "the.mode.key", 440 "the.truststore.key")); 441 assertTrue(ex.getMessage().contains("the.mode.key"), ex.getMessage()); 442 assertTrue(ex.getMessage().contains("the.truststore.key"), ex.getMessage()); 443 } 444 445 @TestTemplate 446 public void testValidateClientAuthTrustStoreAllowsNoneWithoutTrustStore() { 447 // NONE never requests a peer certificate, so a missing truststore is not a misconfiguration. 448 X509Util.validateClientAuthTrustStore(X509Util.ClientAuth.NONE, null, "mode.key", "ts.key"); 449 } 450 451 // A role-scoped password with no role-scoped location would be applied to the legacy keystore. 452 @TestTemplate 453 public void testCreateSSLContextForClientRejectsRoleKeyWithoutRoleLocation() { 454 // The base conf carries a complete legacy keystore, so only the role-scoped location is 455 // missing. 456 conf.set(X509Util.TLS_CONFIG_CLIENT_KEYSTORE_PASSWORD, "orphan-pw"); 457 IllegalArgumentException ex = 458 assertThrows(IllegalArgumentException.class, () -> X509Util.createSslContextForClient(conf)); 459 assertTrue(ex.getMessage().contains(X509Util.TLS_CONFIG_CLIENT_KEYSTORE_PASSWORD), 460 ex.getMessage()); 461 assertTrue(ex.getMessage().contains(X509Util.TLS_CONFIG_CLIENT_KEYSTORE_LOCATION), 462 ex.getMessage()); 463 } 464 465 @TestTemplate 466 public void testResolveStoreRejectsRoleKeyWithoutRoleLocation() { 467 Configuration c = new Configuration(false); 468 c.set("p.role.keystore.password", "pw2"); 469 c.set("p.keystore.location", "/old.jks"); 470 IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, 471 () -> TLSStore.resolve(c, "p.role.", "p.", TLSStore.Keys.RPC_KEYSTORE)); 472 assertTrue(ex.getMessage().contains("p.role.keystore.password"), ex.getMessage()); 473 assertTrue(ex.getMessage().contains("p.role.keystore.location"), ex.getMessage()); 474 } 475 476 /** 477 * A store is a unit: the prefix that supplies the location supplies every other attribute too. 478 * Resolving per key would open the role-scoped file with the legacy password and type. 479 */ 480 @TestTemplate 481 public void testResolveStoreNeverMixesPrefixes() throws Exception { 482 Configuration c = new Configuration(false); 483 c.set("p.role.keystore.location", "/new.p12"); 484 c.set("p.keystore.location", "/old.jks"); 485 c.set("p.keystore.password", "legacy-pw"); 486 c.set("p.keystore.type", "JKS"); 487 488 TLSStore store = TLSStore.resolve(c, "p.role.", "p.", TLSStore.Keys.RPC_KEYSTORE); 489 assertEquals("/new.p12", store.getLocation()); 490 assertNull(store.getPassword(), "password must not be taken from the legacy prefix"); 491 assertEquals("", store.getType(), "type must not be taken from the legacy prefix"); 492 } 493 494 // Deployments that never adopt the role-scoped prefix keep resolving from the legacy keys. 495 @TestTemplate 496 public void testResolveStoreFallsBackToLegacyPrefix() throws Exception { 497 Configuration c = new Configuration(false); 498 c.set("p.keystore.location", "/old.jks"); 499 c.set("p.keystore.password", "pw"); 500 c.set("p.keystore.type", "JKS"); 501 502 TLSStore store = TLSStore.resolve(c, "p.role.", "p.", TLSStore.Keys.RPC_KEYSTORE); 503 assertEquals("/old.jks", store.getLocation()); 504 assertEquals("pw", store.getPassword()); 505 assertEquals("JKS", store.getType()); 506 } 507}