diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 1053cda42f8..f4f55e50f51 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -16,6 +16,8 @@ Improvements
----------------------
* LUCENE-8984: MoreLikeThis MLT is biased for uncommon fields (Andy Hind via Anshum Gupta)
+* SOLR-14223: PKI Auth can bootstrap from existing key files instead of creating new keys on startup (Mike Drob)
+
Other Changes
----------------------
* SOLR-10288: Remove non-minified JavaScript from the webapp. (Erik Hatcher, marcussorealheis)
diff --git a/solr/core/src/java/org/apache/solr/core/CloudConfig.java b/solr/core/src/java/org/apache/solr/core/CloudConfig.java
index 4d0f41097b8..1c65a4605f0 100644
--- a/solr/core/src/java/org/apache/solr/core/CloudConfig.java
+++ b/solr/core/src/java/org/apache/solr/core/CloudConfig.java
@@ -46,10 +46,14 @@ public class CloudConfig {
private final boolean createCollectionCheckLeaderActive;
+ private final String pkiHandlerPrivateKeyPath;
+
+ private final String pkiHandlerPublicKeyPath;
+
CloudConfig(String zkHost, int zkClientTimeout, int hostPort, String hostName, String hostContext, boolean useGenericCoreNames,
int leaderVoteWait, int leaderConflictResolveWait, int autoReplicaFailoverWaitAfterExpiration,
String zkCredentialsProviderClass, String zkACLProviderClass, int createCollectionWaitTimeTillActive,
- boolean createCollectionCheckLeaderActive) {
+ boolean createCollectionCheckLeaderActive, String pkiHandlerPrivateKeyPath, String pkiHandlerPublicKeyPath) {
this.zkHost = zkHost;
this.zkClientTimeout = zkClientTimeout;
this.hostPort = hostPort;
@@ -63,6 +67,8 @@ public class CloudConfig {
this.zkACLProviderClass = zkACLProviderClass;
this.createCollectionWaitTimeTillActive = createCollectionWaitTimeTillActive;
this.createCollectionCheckLeaderActive = createCollectionCheckLeaderActive;
+ this.pkiHandlerPrivateKeyPath = pkiHandlerPrivateKeyPath;
+ this.pkiHandlerPublicKeyPath = pkiHandlerPublicKeyPath;
if (this.hostPort == -1)
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "'hostPort' must be configured to run SolrCloud");
@@ -122,6 +128,14 @@ public class CloudConfig {
return createCollectionCheckLeaderActive;
}
+ public String getPkiHandlerPrivateKeyPath() {
+ return pkiHandlerPrivateKeyPath;
+ }
+
+ public String getPkiHandlerPublicKeyPath() {
+ return pkiHandlerPublicKeyPath;
+ }
+
public static class CloudConfigBuilder {
private static final int DEFAULT_ZK_CLIENT_TIMEOUT = 45000;
@@ -145,6 +159,8 @@ public class CloudConfig {
private String zkACLProviderClass;
private int createCollectionWaitTimeTillActive = DEFAULT_CREATE_COLLECTION_ACTIVE_WAIT;
private boolean createCollectionCheckLeaderActive = DEFAULT_CREATE_COLLECTION_CHECK_LEADER_ACTIVE;
+ private String pkiHandlerPrivateKeyPath;
+ private String pkiHandlerPublicKeyPath;
public CloudConfigBuilder(String hostName, int hostPort) {
this(hostName, hostPort, null);
@@ -206,10 +222,20 @@ public class CloudConfig {
return this;
}
+ public CloudConfigBuilder setPkiHandlerPrivateKeyPath(String pkiHandlerPrivateKeyPath) {
+ this.pkiHandlerPrivateKeyPath = pkiHandlerPrivateKeyPath;
+ return this;
+ }
+
+ public CloudConfigBuilder setPkiHandlerPublicKeyPath(String pkiHandlerPublicKeyPath) {
+ this.pkiHandlerPublicKeyPath = pkiHandlerPublicKeyPath;
+ return this;
+ }
+
public CloudConfig build() {
return new CloudConfig(zkHost, zkClientTimeout, hostPort, hostName, hostContext, useGenericCoreNames, leaderVoteWait,
leaderConflictResolveWait, autoReplicaFailoverWaitAfterExpiration, zkCredentialsProviderClass, zkACLProviderClass, createCollectionWaitTimeTillActive,
- createCollectionCheckLeaderActive);
+ createCollectionCheckLeaderActive, pkiHandlerPrivateKeyPath, pkiHandlerPublicKeyPath);
}
}
}
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index 0039c07d670..9e5033ffaa9 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -21,6 +21,7 @@ import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.security.spec.InvalidKeySpecException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -331,10 +332,14 @@ public class CoreContainer {
}
public CoreContainer(NodeConfig config, Properties properties, CoresLocator locator, boolean asyncSolrCoreLoad) {
+ this.cfg = requireNonNull(config);
this.loader = config.getSolrResourceLoader();
this.solrHome = loader.getInstancePath().toString();
- containerHandlers.put(PublicKeyHandler.PATH, new PublicKeyHandler());
- this.cfg = requireNonNull(config);
+ try {
+ containerHandlers.put(PublicKeyHandler.PATH, new PublicKeyHandler(cfg.getCloudConfig()));
+ } catch (IOException | InvalidKeySpecException e) {
+ throw new RuntimeException("Bad PublicKeyHandler configuration.", e);
+ }
if (null != this.cfg.getBooleanQueryMaxClauseCount()) {
IndexSearcher.setMaxClauseCount(this.cfg.getBooleanQueryMaxClauseCount());
}
diff --git a/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java b/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
index af0b62c1bfd..456cd034314 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
@@ -408,6 +408,12 @@ public class SolrXmlConfig {
case "createCollectionCheckLeaderActive":
builder.setCreateCollectionCheckLeaderActive(Boolean.parseBoolean(value));
break;
+ case "pkiHandlerPrivateKeyPath":
+ builder.setPkiHandlerPrivateKeyPath(value);
+ break;
+ case "pkiHandlerPublicKeyPath":
+ builder.setPkiHandlerPublicKeyPath(value);
+ break;
default:
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unknown configuration parameter in section of solr.xml: " + name);
}
diff --git a/solr/core/src/java/org/apache/solr/security/PublicKeyHandler.java b/solr/core/src/java/org/apache/solr/security/PublicKeyHandler.java
index ad835782a74..208fe6ca91a 100644
--- a/solr/core/src/java/org/apache/solr/security/PublicKeyHandler.java
+++ b/solr/core/src/java/org/apache/solr/security/PublicKeyHandler.java
@@ -17,14 +17,47 @@
package org.apache.solr.security;
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.solr.common.StringUtils;
+import org.apache.solr.core.CloudConfig;
import org.apache.solr.handler.RequestHandlerBase;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.util.CryptoKeys;
+import java.io.IOException;
+import java.net.URL;
+import java.security.spec.InvalidKeySpecException;
+
public class PublicKeyHandler extends RequestHandlerBase {
public static final String PATH = "/admin/info/key";
- final CryptoKeys.RSAKeyPair keyPair = new CryptoKeys.RSAKeyPair();
+
+ final CryptoKeys.RSAKeyPair keyPair;
+
+ @VisibleForTesting
+ public PublicKeyHandler() {
+ keyPair = new CryptoKeys.RSAKeyPair();
+ }
+
+ public PublicKeyHandler(CloudConfig config) throws IOException, InvalidKeySpecException {
+ keyPair = createKeyPair(config);
+ }
+
+ private CryptoKeys.RSAKeyPair createKeyPair(CloudConfig config) throws IOException, InvalidKeySpecException {
+ if (config == null) {
+ return new CryptoKeys.RSAKeyPair();
+ }
+
+ String publicKey = config.getPkiHandlerPublicKeyPath();
+ String privateKey = config.getPkiHandlerPrivateKeyPath();
+
+ // If both properties unset, then we fall back to generating a new key pair
+ if (StringUtils.isEmpty(publicKey) && StringUtils.isEmpty(privateKey)) {
+ return new CryptoKeys.RSAKeyPair();
+ }
+
+ return new CryptoKeys.RSAKeyPair(new URL(privateKey), new URL(publicKey));
+ }
public String getPublicKey() {
return keyPair.getPublicKeyStr();
diff --git a/solr/core/src/java/org/apache/solr/util/CryptoKeys.java b/solr/core/src/java/org/apache/solr/util/CryptoKeys.java
index d8c058f2abb..98b65ef6216 100644
--- a/solr/core/src/java/org/apache/solr/util/CryptoKeys.java
+++ b/solr/core/src/java/org/apache/solr/util/CryptoKeys.java
@@ -24,6 +24,7 @@ import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.InputStream;
import java.lang.invoke.MethodHandles;
+import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
@@ -35,9 +36,10 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
-import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.HashMap;
@@ -52,7 +54,7 @@ import org.slf4j.LoggerFactory;
/**A utility class to verify signatures
*
*/
-public final class CryptoKeys implements CLIO {
+public final class CryptoKeys {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final Map keys;
private Exception exception;
@@ -112,10 +114,14 @@ public final class CryptoKeys implements CLIO {
* Create PublicKey from a .DER file
*/
public static PublicKey getX509PublicKey(byte[] buf)
- throws Exception {
+ throws InvalidKeySpecException {
X509EncodedKeySpec spec = new X509EncodedKeySpec(buf);
- KeyFactory kf = KeyFactory.getInstance("RSA");
- return kf.generatePublic(spec);
+ try {
+ KeyFactory kf = KeyFactory.getInstance("RSA");
+ return kf.generatePublic(spec);
+ } catch (NoSuchAlgorithmException e) {
+ throw new AssertionError("JVM spec is required to support RSA", e);
+ }
}
/**
@@ -243,7 +249,7 @@ public final class CryptoKeys implements CLIO {
public static String decodeAES(String base64CipherTxt, String pwd, final int keySizeBits) {
- final Charset ASCII = Charset.forName("ASCII");
+ final Charset ASCII = StandardCharsets.US_ASCII;
final int INDEX_KEY = 0;
final int INDEX_IV = 1;
final int ITERATIONS = 1;
@@ -309,7 +315,7 @@ public final class CryptoKeys implements CLIO {
}
public static byte[] decryptRSA(byte[] buffer, PublicKey pubKey) throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
- Cipher rsaCipher = null;
+ Cipher rsaCipher;
try {
rsaCipher = Cipher.getInstance("RSA/ECB/nopadding");
} catch (Exception e) {
@@ -317,36 +323,60 @@ public final class CryptoKeys implements CLIO {
}
rsaCipher.init(Cipher.DECRYPT_MODE, pubKey);
return rsaCipher.doFinal(buffer, 0, buffer.length);
-
}
public static class RSAKeyPair {
private final String pubKeyStr;
private final PublicKey publicKey;
private final PrivateKey privateKey;
- private final SecureRandom random = new SecureRandom();
// If this ever comes back to haunt us see the discussion at
// SOLR-9609 for background and code allowing this to go
// into security.json. Also see SOLR-12103.
private static final int DEFAULT_KEYPAIR_LENGTH = 2048;
+ /**
+ * Create an RSA key pair with newly generated keys.
+ */
public RSAKeyPair() {
- KeyPairGenerator keyGen = null;
+ KeyPairGenerator keyGen;
try {
keyGen = KeyPairGenerator.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
- throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
+ throw new AssertionError("JVM spec is required to support RSA", e);
}
keyGen.initialize(DEFAULT_KEYPAIR_LENGTH);
java.security.KeyPair keyPair = keyGen.genKeyPair();
privateKey = keyPair.getPrivate();
publicKey = keyPair.getPublic();
+ pubKeyStr = Base64.byteArrayToBase64(publicKey.getEncoded());
+ }
- X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(
- publicKey.getEncoded());
+ /**
+ * Initialize an RSA key pair from previously saved keys. The formats listed below have been tested, other formats may
+ * also be acceptable but are not guaranteed to work.
+ * @param privateKeyResourceName path to private key file, encoded as a PKCS#8 in a PEM file
+ * @param publicKeyResourceName path to public key file, encoded as X509 in a DER file
+ * @throws IOException if an I/O error occurs reading either key file
+ * @throws InvalidKeySpecException if either key file is inappropriate for an RSA key
+ */
+ public RSAKeyPair(URL privateKeyResourceName, URL publicKeyResourceName) throws IOException, InvalidKeySpecException {
+ try (InputStream inPrivate = privateKeyResourceName.openStream()) {
+ String privateString = new String(inPrivate.readAllBytes(), StandardCharsets.UTF_8)
+ .replaceAll("-----[A-Z ]*-----", "")
+ .replaceAll("\\n", "");
- pubKeyStr = Base64.byteArrayToBase64(x509EncodedKeySpec.getEncoded());
+ PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(Base64.base64ToByteArray(privateString));
+ KeyFactory rsaFactory = KeyFactory.getInstance("RSA");
+ privateKey = rsaFactory.generatePrivate(privateSpec);
+ } catch (NoSuchAlgorithmException e) {
+ throw new AssertionError("JVM spec is required to support RSA", e);
+ }
+
+ try (InputStream inPublic = publicKeyResourceName.openStream()) {
+ publicKey = getX509PublicKey(inPublic.readAllBytes());
+ pubKeyStr = Base64.byteArrayToBase64(publicKey.getEncoded());
+ }
}
public String getPublicKeyStr() {
@@ -359,6 +389,8 @@ public final class CryptoKeys implements CLIO {
public byte[] encrypt(ByteBuffer buffer) {
try {
+ // This is better than nothing, but still not very secure
+ // See: https://crypto.stackexchange.com/questions/20085/which-attacks-are-possible-against-raw-textbook-rsa
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/nopadding");
rsaCipher.init(Cipher.ENCRYPT_MODE, privateKey);
return rsaCipher.doFinal(buffer.array(),buffer.position(), buffer.limit());
@@ -380,17 +412,4 @@ public final class CryptoKeys implements CLIO {
}
}
-
- public static void main(String[] args) throws Exception {
- RSAKeyPair keyPair = new RSAKeyPair();
- CLIO.out(keyPair.getPublicKeyStr());
- PublicKey pk = deserializeX509PublicKey(keyPair.getPublicKeyStr());
- byte[] payload = "Hello World!".getBytes(StandardCharsets.UTF_8);
- byte[] encrypted = keyPair.encrypt(ByteBuffer.wrap(payload));
- String cipherBase64 = Base64.byteArrayToBase64(encrypted);
- CLIO.out("encrypted: "+ cipherBase64);
- CLIO.out("signed: "+ Base64.byteArrayToBase64(keyPair.signSha256(payload)));
- CLIO.out("decrypted "+ new String(decryptRSA(encrypted , pk), StandardCharsets.UTF_8));
- }
-
}
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestRSAKeyPair.java b/solr/core/src/test/org/apache/solr/cloud/TestRSAKeyPair.java
new file mode 100644
index 00000000000..3c5f7934eee
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/cloud/TestRSAKeyPair.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.cloud;
+
+import org.apache.solr.SolrTestCase;
+import org.apache.solr.util.CryptoKeys;
+import org.junit.Test;
+
+import java.net.URL;
+import java.nio.ByteBuffer;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+
+public class TestRSAKeyPair extends SolrTestCase {
+ @Test
+ public void testGenKeyPair() throws Exception {
+ testRoundTrip(new CryptoKeys.RSAKeyPair());
+ }
+
+ @Test
+ public void testReadKeysFromDisk() throws Exception {
+ URL privateKey = getClass().getClassLoader().getResource("cryptokeys/priv_key512_pkcs8.pem");
+ URL publicKey = getClass().getClassLoader().getResource("cryptokeys/pub_key512.der");
+
+ testRoundTrip(new CryptoKeys.RSAKeyPair(privateKey, publicKey));
+ }
+
+ private void testRoundTrip(CryptoKeys.RSAKeyPair kp) throws Exception {
+ final byte[] plaintext = new byte[random().nextInt(64)];
+ random().nextBytes(plaintext);
+
+ byte[] encrypted = kp.encrypt(ByteBuffer.wrap(plaintext));
+ assertThat(plaintext, not(equalTo(encrypted)));
+
+ byte[] decrypted = CryptoKeys.decryptRSA(encrypted, kp.getPublicKey());
+
+ assertTrue("Decrypted text is shorter than original text.", decrypted.length >= plaintext.length);
+
+ // Pad with null bytes because RSAKeyPair uses RSA/ECB/NoPadding
+ int pad = decrypted.length - plaintext.length;
+ final byte[] padded = new byte[decrypted.length];
+ System.arraycopy(plaintext, 0, padded, pad, plaintext.length);
+ assertArrayEquals(padded, decrypted);
+ }
+}
diff --git a/solr/core/src/test/org/apache/solr/filestore/TestDistribPackageStore.java b/solr/core/src/test/org/apache/solr/filestore/TestDistribPackageStore.java
index f1681e4f39c..71df283c9ea 100644
--- a/solr/core/src/test/org/apache/solr/filestore/TestDistribPackageStore.java
+++ b/solr/core/src/test/org/apache/solr/filestore/TestDistribPackageStore.java
@@ -17,8 +17,8 @@
package org.apache.solr.filestore;
-import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map;
@@ -52,6 +52,7 @@ import org.junit.Before;
import static org.apache.solr.common.util.Utils.JAVABINCONSUMER;
import static org.apache.solr.core.TestDynamicLoading.getFileContent;
+import static org.hamcrest.CoreMatchers.containsString;
@LogLevel("org.apache.solr.filestore.PackageStoreAPI=DEBUG;org.apache.solr.filestore.DistribPackageStore=DEBUG")
public class TestDistribPackageStore extends SolrCloudTestCase {
@@ -86,7 +87,7 @@ public class TestDistribPackageStore extends SolrCloudTestCase {
);
fail("should have failed because of wrong signature ");
} catch (RemoteExecutionException e) {
- assertTrue(e.getMessage().contains("Signature does not match"));
+ assertThat(e.getMessage(), containsString("Signature does not match"));
}
postFile(cluster.getSolrClient(), getFileContent("runtimecode/runtimelibs.jar.bin"),
@@ -275,12 +276,15 @@ public class TestDistribPackageStore extends SolrCloudTestCase {
assertEquals(name, rsp.getResponse().get(CommonParams.FILE));
}
+ /**
+ * Read and return the contents of the file-like resource
+ * @param fname the name of the resource to read
+ * @return the bytes of the resource
+ * @throws IOException if there is an I/O error reading the contents
+ */
public static byte[] readFile(String fname) throws IOException {
- byte[] buf = null;
- try (FileInputStream fis = new FileInputStream(getFile(fname))) {
- buf = new byte[fis.available()];
- fis.read(buf);
+ try (InputStream is = TestDistribPackageStore.class.getClassLoader().getResourceAsStream(fname)) {
+ return is.readAllBytes();
}
- return buf;
}
}
diff --git a/solr/core/src/test/org/apache/solr/pkg/TestPackages.java b/solr/core/src/test/org/apache/solr/pkg/TestPackages.java
index 897383a1602..55f3c1b79cd 100644
--- a/solr/core/src/test/org/apache/solr/pkg/TestPackages.java
+++ b/solr/core/src/test/org/apache/solr/pkg/TestPackages.java
@@ -65,6 +65,7 @@ import static org.apache.solr.core.TestDynamicLoading.getFileContent;
import static org.apache.solr.filestore.TestDistribPackageStore.readFile;
import static org.apache.solr.filestore.TestDistribPackageStore.uploadKey;
import static org.apache.solr.filestore.TestDistribPackageStore.waitForAllNodesHaveFile;
+import static org.hamcrest.CoreMatchers.containsString;
@LogLevel("org.apache.solr.pkg.PackageLoader=DEBUG;org.apache.solr.pkg.PackageAPI=DEBUG")
//@org.apache.lucene.util.LuceneTestCase.AwaitsFix(bugUrl="https://issues.apache.org/jira/browse/SOLR-13822") // leaks files
@@ -587,9 +588,7 @@ public class TestPackages extends SolrCloudTestCase {
fail("should have failed with message : " + expectErrorMsg);
} catch (BaseHttpSolrClient.RemoteExecutionException e) {
String msg = e.getMetaData()._getStr(errPath, "");
- assertTrue("should have failed with message: " + expectErrorMsg + "actual message : " + msg,
- msg.contains(expectErrorMsg)
- );
+ assertThat(msg, containsString(expectErrorMsg));
}
}
}
diff --git a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
index 58d44a76fb8..71599fa3059 100644
--- a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
+++ b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
@@ -293,6 +293,10 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
System.setProperty("solr.clustering.enabled", "false");
System.setProperty("solr.peerSync.useRangeVersions", String.valueOf(random().nextBoolean()));
System.setProperty("solr.cloud.wait-for-updates-with-stale-state-pause", "500");
+
+ System.setProperty("pkiHandlerPrivateKeyPath", SolrTestCaseJ4.class.getClassLoader().getResource("cryptokeys/priv_key512_pkcs8.pem").toExternalForm());
+ System.setProperty("pkiHandlerPublicKeyPath", SolrTestCaseJ4.class.getClassLoader().getResource("cryptokeys/pub_key512.der").toExternalForm());
+
System.setProperty(ZK_WHITELIST_PROPERTY, "*");
startTrackingSearchers();
ignoreException("ignore_exception");
diff --git a/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java b/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java
index fbb547c9796..beb37fb763e 100644
--- a/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java
+++ b/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java
@@ -113,6 +113,8 @@ public class MiniSolrCloudCluster {
" ${distribUpdateSoTimeout:340000}\n" +
" ${zkCredentialsProvider:org.apache.solr.common.cloud.DefaultZkCredentialsProvider} \n" +
" ${zkACLProvider:org.apache.solr.common.cloud.DefaultZkACLProvider} \n" +
+ " ${pkiHandlerPrivateKeyPath:cryptokeys/priv_key512_pkcs8.pem} \n" +
+ " ${pkiHandlerPublicKeyPath:cryptokeys/pub_key512.der} \n" +
" \n" +
" \n" +
" \n" +
diff --git a/solr/core/src/test-files/cryptokeys/priv_key512.pem b/solr/test-framework/src/resources/cryptokeys/priv_key512.pem
similarity index 100%
rename from solr/core/src/test-files/cryptokeys/priv_key512.pem
rename to solr/test-framework/src/resources/cryptokeys/priv_key512.pem
diff --git a/solr/test-framework/src/resources/cryptokeys/priv_key512_pkcs8.pem b/solr/test-framework/src/resources/cryptokeys/priv_key512_pkcs8.pem
new file mode 100644
index 00000000000..4fbeb6e0755
--- /dev/null
+++ b/solr/test-framework/src/resources/cryptokeys/priv_key512_pkcs8.pem
@@ -0,0 +1,10 @@
+-----BEGIN PRIVATE KEY-----
+MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEAyCZJV+X4TY2P+goA
+/ZNx5aHXumTpSCzDkDmcsf454wH9Z5YlmD80QN1rxJd9AMRVGbDk/7YhnKk8BLN8
+KtzrIQIDAQABAkBtkXATO2TL59RKuFFEgAQZBplXg8ilZ0QD31Ylppu/5ixY3lRR
+3vL4S+7nSaEfdFAjFB35HpJizWR706UeVXZxAiEA/zfruDJtUS7YD4KDV8T29sd+
+ceu6ukNCuH2vRkqihO0CIQDIwzG+UeVKHpDZxRNLG7JQRpB835bh596GB8hYFWXM
+hQIgQcExnSp42cK87foNRu67RkeNv2IhoN21cf0HzI9sId0CIBpXejRlnHcwMYNR
+V2m4dZoQ2C56S9rSSKE/bisYi6XdAiA2plITMDZqhB00+XmgN+SGoAaOzmlvuouC
+2Zcm9WGL9A==
+-----END PRIVATE KEY-----
diff --git a/solr/core/src/test-files/cryptokeys/pub_key512.der b/solr/test-framework/src/resources/cryptokeys/pub_key512.der
similarity index 100%
rename from solr/core/src/test-files/cryptokeys/pub_key512.der
rename to solr/test-framework/src/resources/cryptokeys/pub_key512.der
diff --git a/solr/test-framework/src/test/org/apache/solr/cloud/MiniSolrCloudClusterTest.java b/solr/test-framework/src/test/org/apache/solr/cloud/MiniSolrCloudClusterTest.java
index 2fb4dba87ce..2bbf33f2d4f 100644
--- a/solr/test-framework/src/test/org/apache/solr/cloud/MiniSolrCloudClusterTest.java
+++ b/solr/test-framework/src/test/org/apache/solr/cloud/MiniSolrCloudClusterTest.java
@@ -22,7 +22,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import com.carrotsearch.randomizedtesting.rules.SystemPropertiesRestoreRule;
import org.apache.lucene.util.LuceneTestCase;
-import org.apache.solr.SolrTestCase;
+import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.client.solrj.embedded.JettyConfig;
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
import org.apache.solr.util.RevertDefaultThreadHandlerRule;
@@ -32,7 +32,7 @@ import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
@LuceneTestCase.SuppressSysoutChecks(bugUrl = "Solr logs to JUL")
-public class MiniSolrCloudClusterTest extends SolrTestCase {
+public class MiniSolrCloudClusterTest extends SolrTestCaseJ4 {
@ClassRule
public static TestRule solrClassRules = RuleChain.outerRule(