package ch.ge.ve.commons.crypto;
/*-
* #%L
* Common crypto utilities
* %%
* Copyright (C) 2015 - 2016 République et Canton de Genève
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
import ch.ge.ve.commons.crypto.exceptions.CryptoConfigurationRuntimeException;
import ch.ge.ve.commons.crypto.utils.CipherFactory;
import ch.ge.ve.commons.crypto.utils.MacFactory;
import ch.ge.ve.commons.crypto.utils.SecureRandomFactory;
import ch.ge.ve.commons.properties.PropertyConfigurationException;
import ch.ge.ve.commons.properties.PropertyConfigurationService;
import org.apache.commons.io.IOUtils;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import java.io.*;
import java.security.SecureRandom;
/**
* This class provides the default implementation for a CryptoUtilsConfiguration
*/
public class SensitiveDataCryptoUtilsConfigurationDefaultImpl implements SensitiveDataCryptoUtilsConfiguration {
public static final String COMMON_CRYPTO_STORAGE_ALGORITHM = "common.crypto.storage.algorithm";
public static final String COMMON_CRYPTO_STORAGE_BLOCKMODE = "common.crypto.storage.blockmode";
public static final String COMMON_CRYPTO_STREAM_MAX_BYTES = "common.crypto.stream.max.bytes";
private final SecureRandom SECURE_RANDOM = SecureRandomFactory.createPRNG();
private PropertyConfigurationService propertyConfigurationService;
// Mac and Cipher are not thread safe and thus cannot be declared as a static member used concurrently,
// but we need to limit the instance creations for performance optimization. ThreadLocal scope
// is the best way to ensure that there is no sharing between threads of the same object, and
// that they are reused as much as possible (for voting cards generation for example).
private ThreadLocal<Mac> macThreadLocal = new ThreadLocal<>();
private ThreadLocal<Cipher> cipherThreadLocal = new ThreadLocal<>();
// no thread safety concern on the secret key
private SecretKey secretKey;
public SensitiveDataCryptoUtilsConfigurationDefaultImpl(PropertyConfigurationService propertyConfigurationService) {
this.propertyConfigurationService = propertyConfigurationService;
}
@Override
public Cipher getCipher() {
if (cipherThreadLocal.get() == null) {
try {
String algorithm = propertyConfigurationService.getConfigValue(COMMON_CRYPTO_STORAGE_ALGORITHM);
String blockMode = propertyConfigurationService.getConfigValue(COMMON_CRYPTO_STORAGE_BLOCKMODE);
cipherThreadLocal.set(new CipherFactory(propertyConfigurationService).getInstance(algorithm + blockMode));
} catch (PropertyConfigurationException e) {
throw new CryptoConfigurationRuntimeException("Unable to load the sensitive data cipher:", e);
}
}
return cipherThreadLocal.get();
}
@Override
public Mac getMac() {
if (macThreadLocal.get() == null) {
macThreadLocal.set(new MacFactory(propertyConfigurationService).getInstance());
}
return macThreadLocal.get();
}
@Override
public SecretKey getSecretKey() {
if (secretKey == null) {
ObjectInputStream ois = null;
try {
final InputStream hmacKeyInputStream = getPasswordHMACKeyInputStream();
ois = new ObjectInputStream(hmacKeyInputStream);
secretKey = (SecretKey) ois.readObject();
} catch (ClassNotFoundException | PropertyConfigurationException | IOException e) {
throw new CryptoConfigurationRuntimeException("Unable to retrieve the secret key from the file system:", e);
} finally {
if (ois != null) {
IOUtils.closeQuietly(ois);
}
}
}
return secretKey;
}
@Override
public int getIterations() {
try {
int minIterations = propertyConfigurationService.getConfigValueAsInt("common.crypto.pbkdf.min.iterations");
int maxIterations = propertyConfigurationService.getConfigValueAsInt("common.crypto.pbkdf.max.iterations");
return minIterations + SECURE_RANDOM.nextInt(maxIterations - minIterations + 1);
} catch (PropertyConfigurationException e) {
throw new CryptoConfigurationRuntimeException("cannot find pbkdf2 iterations configuration", e);
}
}
@Override
public String getPbkdf2Algorithm() {
try {
return propertyConfigurationService.getConfigValue("common.crypto.pbkdf.algorithm");
} catch (PropertyConfigurationException e) {
throw new CryptoConfigurationRuntimeException("cannot find pbkdf2 algorithm configuration", e);
}
}
@Override
public long getSealMaxBytes() {
try {
return propertyConfigurationService.getConfigValueAsLong(COMMON_CRYPTO_STREAM_MAX_BYTES);
} catch (PropertyConfigurationException e) {
throw new CryptoConfigurationRuntimeException("cannot find seal object max bytes configuration", e);
}
}
private InputStream getPasswordHMACKeyInputStream() throws FileNotFoundException, PropertyConfigurationException {
final String hmacKeyPath = propertyConfigurationService.getConfigValue("password.hmac.key.filename");
if (new File(hmacKeyPath).exists()) {
// first try to find on file system
return new FileInputStream(hmacKeyPath);
} else {
// try to find in classpath (useful for test classes)
final InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream(hmacKeyPath);
if (resourceAsStream != null) {
return resourceAsStream;
} else {
throw new CryptoConfigurationRuntimeException("Cannot find password hmac key from filesystem or classpath: " + hmacKeyPath);
}
}
}
}