You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by kr...@apache.org on 2023/01/10 17:01:04 UTC

[solr] branch branch_9x updated: SOLR-16613: CryptoKeys should handle RSA padding for OpenJ9 (#1279)

This is an automated email from the ASF dual-hosted git repository.

krisden pushed a commit to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/branch_9x by this push:
     new f9b486c4954 SOLR-16613: CryptoKeys should handle RSA padding for OpenJ9 (#1279)
f9b486c4954 is described below

commit f9b486c4954df469f6decc5c464664f3c9093fee
Author: Kevin Risden <ri...@users.noreply.github.com>
AuthorDate: Tue Jan 10 11:52:04 2023 -0500

    SOLR-16613: CryptoKeys should handle RSA padding for OpenJ9 (#1279)
---
 solr/CHANGES.txt                                   |  2 ++
 .../src/java/org/apache/solr/util/CryptoKeys.java  | 32 ++++++++++++++++++----
 .../test/org/apache/solr/cloud/TestRSAKeyPair.java | 22 +++++++++------
 3 files changed, 43 insertions(+), 13 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 1b71262252e..a1244b2b650 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -137,6 +137,8 @@ Bug Fixes
 
 * SOLR-16611: NullPointerException occus when there are no segments in `{!collapse hint=top_fc}` (Minami Takuya via Mikhail Khludnev)
 
+* SOLR-16613: CryptoKeys should handle RSA padding for OpenJ9 (Kevin Risden)
+
 Build
 ---------------------
 * Upgrade forbiddenapis to 3.4 (Uwe Schindler)
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 693724d43a2..2d87aa2e38a 100644
--- a/solr/core/src/java/org/apache/solr/util/CryptoKeys.java
+++ b/solr/core/src/java/org/apache/solr/util/CryptoKeys.java
@@ -35,6 +35,7 @@ import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPrivateKey;
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.spec.X509EncodedKeySpec;
@@ -54,6 +55,9 @@ import org.slf4j.LoggerFactory;
 /** A utility class with helpers for various signature and certificate tasks */
 public final class CryptoKeys {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  private static final String CIPHER_ALGORITHM = "RSA/ECB/nopadding";
+
   private final Map<String, PublicKey> keys;
   private Exception exception;
 
@@ -168,12 +172,12 @@ public final class CryptoKeys {
       throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
     Cipher rsaCipher;
     try {
-      rsaCipher = Cipher.getInstance("RSA/ECB/nopadding");
+      rsaCipher = Cipher.getInstance(CIPHER_ALGORITHM);
     } catch (Exception e) {
       throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
     }
     rsaCipher.init(Cipher.DECRYPT_MODE, pubKey);
-    return rsaCipher.doFinal(buffer, 0, buffer.length);
+    return rsaCipher.doFinal(buffer);
   }
 
   public static boolean verifySha256(byte[] data, byte[] sig, PublicKey key)
@@ -242,6 +246,8 @@ public final class CryptoKeys {
     // into security.json. Also see SOLR-12103.
     private static final int DEFAULT_KEYPAIR_LENGTH = 2048;
 
+    private final int keySizeInBytes;
+
     /** Create an RSA key pair with newly generated keys. */
     public RSAKeyPair() {
       KeyPairGenerator keyGen;
@@ -253,6 +259,7 @@ public final class CryptoKeys {
       keyGen.initialize(DEFAULT_KEYPAIR_LENGTH);
       java.security.KeyPair keyPair = keyGen.genKeyPair();
       privateKey = keyPair.getPrivate();
+      keySizeInBytes = determineKeySizeInBytes(privateKey);
       publicKey = keyPair.getPublic();
       pubKeyStr = Base64.getEncoder().encodeToString(publicKey.getEncoded());
     }
@@ -277,6 +284,7 @@ public final class CryptoKeys {
             new PKCS8EncodedKeySpec(Base64.getMimeDecoder().decode(privateString));
         KeyFactory rsaFactory = KeyFactory.getInstance("RSA");
         privateKey = rsaFactory.generatePrivate(privateSpec);
+        keySizeInBytes = determineKeySizeInBytes(privateKey);
       } catch (NoSuchAlgorithmException e) {
         throw new AssertionError("JVM spec is required to support RSA", e);
       }
@@ -295,15 +303,29 @@ public final class CryptoKeys {
       return publicKey;
     }
 
+    private int determineKeySizeInBytes(PrivateKey privateKey) {
+      return ((RSAPrivateKey) privateKey).getModulus().bitLength() / Byte.SIZE;
+    }
+
+    // Used for testing
+    public int getKeySizeInBytes() {
+      return keySizeInBytes;
+    }
+
     public byte[] encrypt(ByteBuffer buffer) {
+      // This is necessary to pad the plaintext to match the exact size of the keysize in openj9.
+      // OpenJDK seems to do this padding internally, but OpenJ9 does not pad the byte input to
+      // the key size in bytes without padding. This only works with "RSA/ECB/nopadding".
+      byte[] paddedPlaintext = new byte[getKeySizeInBytes()];
+      buffer.get(paddedPlaintext, buffer.arrayOffset() + buffer.position(), buffer.limit());
+
       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");
+        Cipher rsaCipher = Cipher.getInstance(CIPHER_ALGORITHM);
         rsaCipher.init(Cipher.ENCRYPT_MODE, privateKey);
-        return rsaCipher.doFinal(
-            buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.limit());
+        return rsaCipher.doFinal(paddedPlaintext);
       } catch (Exception e) {
         throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
       }
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestRSAKeyPair.java b/solr/core/src/test/org/apache/solr/cloud/TestRSAKeyPair.java
index 0fc0c87ad6b..1e614cb7a34 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestRSAKeyPair.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestRSAKeyPair.java
@@ -21,6 +21,9 @@ import static org.hamcrest.CoreMatchers.not;
 
 import java.net.URL;
 import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import org.apache.lucene.tests.util.TestUtil;
 import org.apache.solr.SolrTestCase;
 import org.apache.solr.util.CryptoKeys;
 import org.hamcrest.MatcherAssert;
@@ -36,13 +39,16 @@ public class TestRSAKeyPair extends SolrTestCase {
   public void testReadKeysFromDisk() throws Exception {
     URL privateKey = getClass().getClassLoader().getResource("cryptokeys/priv_key512_pkcs8.pem");
     URL publicKey = getClass().getClassLoader().getResource("cryptokeys/pub_key512.der");
-
+    assertNotNull(privateKey);
+    assertNotNull(publicKey);
     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);
+    int keySizeInBytes = kp.getKeySizeInBytes();
+    // Max size of the plaintext can only be as big as the key in bytes with no padding
+    String plaintextString = TestUtil.randomSimpleString(random(), keySizeInBytes);
+    final byte[] plaintext = plaintextString.getBytes(StandardCharsets.UTF_8);
 
     byte[] encrypted = kp.encrypt(ByteBuffer.wrap(plaintext));
     MatcherAssert.assertThat(plaintext, not(equalTo(encrypted)));
@@ -52,10 +58,10 @@ public class TestRSAKeyPair extends SolrTestCase {
     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);
+    // Strip off any null bytes RSAKeyPair uses RSA/ECB/NoPadding and during decryption null bytes
+    // can be left.
+    // Under "Known Limitations"
+    // https://www.ibm.com/docs/en/sdk-java-technology/8?topic=guide-ibmjceplus-ibmjceplusfips-providers
+    assertArrayEquals(plaintext, Arrays.copyOf(decrypted, plaintext.length));
   }
 }