mirror of https://github.com/apache/lucene.git
SOLR-4392: Make it possible to specify AES encrypted password in dataconfig.xml
git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1678195 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
be399a96d5
commit
3e20086499
|
@ -169,6 +169,8 @@ New Features
|
|||
|
||||
* SOLR-6220: Rule Based Replica Assignment during collection creation (Noble Paul)
|
||||
|
||||
* SOLR-4392: Make it possible to specify AES encrypted password in dataconfig.xml (Noble Paul)
|
||||
|
||||
|
||||
Bug Fixes
|
||||
----------------------
|
||||
|
|
|
@ -19,12 +19,16 @@ package org.apache.solr.handler.dataimport;
|
|||
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.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.sql.*;
|
||||
|
@ -61,6 +65,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 +106,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 (FileReader fr = new FileReader(encryptionKey)) {
|
||||
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 +428,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) {
|
||||
|
|
|
@ -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;
|
||||
|
@ -126,6 +129,36 @@ public class TestJdbcDataSource extends AbstractDataImportHandlerTestCase {
|
|||
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);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRetrieveFromJndiFailureNotHidden() throws Exception {
|
||||
MockInitialContextFactory.bind("java:comp/env/jdbc/JndiDB", dataSource);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue