You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by no...@apache.org on 2015/05/07 20:31:35 UTC

svn commit: r1678250 - in /lucene/dev/trunk/solr: ./ contrib/dataimporthandler/src/java/org/apache/solr/handler/dataimport/ contrib/dataimporthandler/src/test/org/apache/solr/handler/dataimport/ core/src/java/org/apache/solr/util/

Author: noble
Date: Thu May  7 18:31:35 2015
New Revision: 1678250

URL: http://svn.apache.org/r1678250
Log:
SOLR-4392: Make it possible to specify AES encrypted password in dataconfig.xml 

Modified:
    lucene/dev/trunk/solr/CHANGES.txt
    lucene/dev/trunk/solr/contrib/dataimporthandler/src/java/org/apache/solr/handler/dataimport/JdbcDataSource.java
    lucene/dev/trunk/solr/contrib/dataimporthandler/src/test/org/apache/solr/handler/dataimport/TestJdbcDataSource.java
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/CryptoKeys.java

Modified: lucene/dev/trunk/solr/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/CHANGES.txt?rev=1678250&r1=1678249&r2=1678250&view=diff
==============================================================================
--- lucene/dev/trunk/solr/CHANGES.txt (original)
+++ lucene/dev/trunk/solr/CHANGES.txt Thu May  7 18:31:35 2015
@@ -172,6 +172,9 @@ New Features
 * SOLR-6968: New 'cardinality' option for stats.field, uses HyperLogLog to efficiently 
   estimate the cardinality of a field w/bounded RAM. (hossman)
 
+* SOLR-4392: Make it possible to specify AES encrypted password in dataconfig.xml (Noble Paul)
+
+
 Bug Fixes
 ----------------------
 

Modified: lucene/dev/trunk/solr/contrib/dataimporthandler/src/java/org/apache/solr/handler/dataimport/JdbcDataSource.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/contrib/dataimporthandler/src/java/org/apache/solr/handler/dataimport/JdbcDataSource.java?rev=1678250&r1=1678249&r2=1678250&view=diff
==============================================================================
--- lucene/dev/trunk/solr/contrib/dataimporthandler/src/java/org/apache/solr/handler/dataimport/JdbcDataSource.java (original)
+++ lucene/dev/trunk/solr/contrib/dataimporthandler/src/java/org/apache/solr/handler/dataimport/JdbcDataSource.java Thu May  7 18:31:35 2015
@@ -16,17 +16,26 @@
  */
 package org.apache.solr.handler.dataimport;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.apache.solr.handler.dataimport.DataImportHandlerException.wrapAndThrow;
 import static org.apache.solr.handler.dataimport.DataImportHandlerException.SEVERE;
 
+import org.apache.solr.common.SolrException;
+import org.apache.solr.util.CryptoKeys;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javax.naming.InitialContext;
 import javax.naming.NamingException;
 
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
 import java.sql.*;
 import java.util.*;
 import java.util.concurrent.Callable;
@@ -61,6 +70,7 @@ public class JdbcDataSource extends
 
   @Override
   public void init(Context context, Properties initProps) {
+    initProps = decryptPwd(initProps);
     Object o = initProps.get(CONVERT_TYPE);
     if (o != null)
       convertType = Boolean.parseBoolean(o.toString());
@@ -101,6 +111,34 @@ public class JdbcDataSource extends
     }
   }
 
+  private Properties decryptPwd(Properties initProps) {
+    String encryptionKey = initProps.getProperty("encryptKeyFile");
+    if (initProps.getProperty("password") != null && encryptionKey != null) {
+      // this means the password is encrypted and use the file to decode it
+      try {
+        try (Reader fr = new InputStreamReader(new FileInputStream(encryptionKey), UTF_8)) {
+          char[] chars = new char[100];//max 100 char password
+          int len = fr.read(chars);
+          if (len < 6)
+            throw new DataImportHandlerException(SEVERE, "There should be a password of length 6 atleast " + encryptionKey);
+          Properties props = new Properties();
+          props.putAll(initProps);
+          String password = null;
+          try {
+            password = CryptoKeys.decodeAES(initProps.getProperty("password"), new String(chars, 0, len)).trim();
+          } catch (SolrException se) {
+            throw new DataImportHandlerException(SEVERE, "Error decoding password", se.getCause());
+          }
+          props.put("password", password);
+          initProps = props;
+        }
+      } catch (IOException e) {
+        throw new DataImportHandlerException(SEVERE, "Could not load encryptKeyFile  " + encryptionKey);
+      }
+    }
+    return initProps;
+  }
+
   protected Callable<Connection> createConnectionFactory(final Context context,
                                        final Properties initProps) {
 //    final VariableResolver resolver = context.getVariableResolver();
@@ -395,7 +433,7 @@ public class JdbcDataSource extends
     }
   }
 
-  private Connection getConnection() throws Exception {
+  Connection getConnection() throws Exception {
     long currTime = System.nanoTime();
     if (currTime - connLastUsed > CONN_TIME_OUT) {
       synchronized (this) {

Modified: lucene/dev/trunk/solr/contrib/dataimporthandler/src/test/org/apache/solr/handler/dataimport/TestJdbcDataSource.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/contrib/dataimporthandler/src/test/org/apache/solr/handler/dataimport/TestJdbcDataSource.java?rev=1678250&r1=1678249&r2=1678250&view=diff
==============================================================================
--- lucene/dev/trunk/solr/contrib/dataimporthandler/src/test/org/apache/solr/handler/dataimport/TestJdbcDataSource.java (original)
+++ lucene/dev/trunk/solr/contrib/dataimporthandler/src/test/org/apache/solr/handler/dataimport/TestJdbcDataSource.java Thu May  7 18:31:35 2015
@@ -16,6 +16,9 @@
  */
 package org.apache.solr.handler.dataimport;
 
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 import java.sql.Connection;
 import java.sql.Driver;
 import java.sql.DriverManager;
@@ -123,6 +126,36 @@ public class TestJdbcDataSource extends
 
     mockControl.verify();
 
+    assertSame("connection", conn, connection);
+  }
+
+  @Test
+  public void testRetrieveFromJndiWithCredentialsWithEncryptedPwd() throws Exception {
+    MockInitialContextFactory.bind("java:comp/env/jdbc/JndiDB", dataSource);
+    File tmpdir = File.createTempFile("test", "tmp", createTempDir().toFile());
+    Files.delete(tmpdir.toPath());
+    tmpdir.mkdir();
+    byte[] content = "secret".getBytes(StandardCharsets.UTF_8);
+    createFile(tmpdir, "enckeyfile.txt", content, false);
+
+    props.put(JdbcDataSource.JNDI_NAME, "java:comp/env/jdbc/JndiDB");
+    props.put("user", "Fred");
+    props.put("encryptKeyFile", new File(tmpdir, "enckeyfile.txt").getAbsolutePath());
+    props.put("password", "U2FsdGVkX18QMjY0yfCqlfBMvAB4d3XkwY96L7gfO2o=");
+    props.put("holdability", "HOLD_CURSORS_OVER_COMMIT");
+    EasyMock.expect(dataSource.getConnection("Fred", "MyPassword")).andReturn(
+        connection);
+    jdbcDataSource.init(context, props);
+
+    connection.setAutoCommit(false);
+    connection.setHoldability(1);
+
+    mockControl.replay();
+
+    Connection conn = jdbcDataSource.getConnection();
+
+    mockControl.verify();
+
     assertSame("connection", conn, connection);
   }
 

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/CryptoKeys.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/CryptoKeys.java?rev=1678250&r1=1678249&r2=1678250&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/CryptoKeys.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/CryptoKeys.java Thu May  7 18:31:35 2015
@@ -17,17 +17,27 @@ package org.apache.solr.util;
  * limitations under the License.
  */
 
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
 import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.security.GeneralSecurityException;
 import java.security.InvalidKeyException;
 import java.security.KeyFactory;
+import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.PublicKey;
 import java.security.Signature;
 import java.security.SignatureException;
 import java.security.spec.X509EncodedKeySpec;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 
+import org.apache.solr.common.SolrException;
 import org.apache.solr.common.util.Base64;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -107,5 +117,139 @@ public final class CryptoKeys {
     return false;
   }
 
+  private static byte[][] evpBytesTokey(int key_len, int iv_len, MessageDigest md,
+                                        byte[] salt, byte[] data, int count) {
+    byte[][] both = new byte[2][];
+    byte[] key = new byte[key_len];
+    int key_ix = 0;
+    byte[] iv = new byte[iv_len];
+    int iv_ix = 0;
+    both[0] = key;
+    both[1] = iv;
+    byte[] md_buf = null;
+    int nkey = key_len;
+    int niv = iv_len;
+    int i = 0;
+    if (data == null) {
+      return both;
+    }
+    int addmd = 0;
+    for (; ; ) {
+      md.reset();
+      if (addmd++ > 0) {
+        md.update(md_buf);
+      }
+      md.update(data);
+      if (null != salt) {
+        md.update(salt, 0, 8);
+      }
+      md_buf = md.digest();
+      for (i = 1; i < count; i++) {
+        md.reset();
+        md.update(md_buf);
+        md_buf = md.digest();
+      }
+      i = 0;
+      if (nkey > 0) {
+        for (; ; ) {
+          if (nkey == 0)
+            break;
+          if (i == md_buf.length)
+            break;
+          key[key_ix++] = md_buf[i];
+          nkey--;
+          i++;
+        }
+      }
+      if (niv > 0 && i != md_buf.length) {
+        for (; ; ) {
+          if (niv == 0)
+            break;
+          if (i == md_buf.length)
+            break;
+          iv[iv_ix++] = md_buf[i];
+          niv--;
+          i++;
+        }
+      }
+      if (nkey == 0 && niv == 0) {
+        break;
+      }
+    }
+    for (i = 0; i < md_buf.length; i++) {
+      md_buf[i] = 0;
+    }
+    return both;
+  }
+
+  public static String decodeAES(String base64CipherTxt, String pwd) {
+    int[] strengths = new int[]{256, 192, 128};
+    Exception e = null;
+    for (int strength : strengths) {
+      try {
+        return decodeAES(base64CipherTxt, pwd, strength);
+      } catch (Exception exp) {
+        e = exp;
+      }
+    }
+    throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Error decoding ", e);
+  }
+
+
+  public static String decodeAES(String base64CipherTxt, String pwd, final int keySizeBits) {
+    final Charset ASCII = Charset.forName("ASCII");
+    final int INDEX_KEY = 0;
+    final int INDEX_IV = 1;
+    final int ITERATIONS = 1;
+    final int SALT_OFFSET = 8;
+    final int SALT_SIZE = 8;
+    final int CIPHERTEXT_OFFSET = SALT_OFFSET + SALT_SIZE;
+
+    try {
+      byte[] headerSaltAndCipherText = Base64.base64ToByteArray(base64CipherTxt);
+
+      // --- extract salt & encrypted ---
+      // header is "Salted__", ASCII encoded, if salt is being used (the default)
+      byte[] salt = Arrays.copyOfRange(
+          headerSaltAndCipherText, SALT_OFFSET, SALT_OFFSET + SALT_SIZE);
+      byte[] encrypted = Arrays.copyOfRange(
+          headerSaltAndCipherText, CIPHERTEXT_OFFSET, headerSaltAndCipherText.length);
+
+      // --- specify cipher and digest for evpBytesTokey method ---
+
+      Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
+      MessageDigest md5 = MessageDigest.getInstance("MD5");
+
+      // --- create key and IV  ---
+
+      // the IV is useless, OpenSSL might as well have use zero's
+      final byte[][] keyAndIV = evpBytesTokey(
+          keySizeBits / Byte.SIZE,
+          aesCBC.getBlockSize(),
+          md5,
+          salt,
+          pwd.getBytes(ASCII),
+          ITERATIONS);
+
+      SecretKeySpec key = new SecretKeySpec(keyAndIV[INDEX_KEY], "AES");
+      IvParameterSpec iv = new IvParameterSpec(keyAndIV[INDEX_IV]);
+
+      // --- initialize cipher instance and decrypt ---
+
+      aesCBC.init(Cipher.DECRYPT_MODE, key, iv);
+      byte[] decrypted = aesCBC.doFinal(encrypted);
+      return new String(decrypted, ASCII);
+    } catch (BadPaddingException e) {
+      // AKA "something went wrong"
+      throw new IllegalStateException(
+          "Bad password, algorithm, mode or padding;" +
+              " no salt, wrong number of iterations or corrupted ciphertext.", e);
+    } catch (IllegalBlockSizeException e) {
+      throw new IllegalStateException(
+          "Bad algorithm, mode or corrupted (resized) ciphertext.", e);
+    } catch (GeneralSecurityException e) {
+      throw new IllegalStateException(e);
+    }
+  }
 
 }