/** * Copyright (c) 2009 - 2012 Red Hat, Inc. * * This software is licensed to you under the GNU General Public License, * version 2 (GPLv2). There is NO WARRANTY for this software, express or * implied, including the implied warranties of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 * along with this software; if not, see * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. * * Red Hat trademarks are not licensed under GPLv2. No permission is * granted to use or replicate Red Hat trademarks that are incorporated * in this software or its documentation. */ package org.candlepin.common.config; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.nio.charset.Charset; import java.util.Arrays; import java.util.Properties; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; /** * EncryptedValueConfigurationParser */ public class EncryptedConfiguration extends PropertiesFileConfiguration { private static Logger log = LoggerFactory.getLogger(EncryptedConfiguration.class); private String passphrase = null; public EncryptedConfiguration() { super(); } public EncryptedConfiguration(String fileName) throws ConfigurationException { super(fileName); } public EncryptedConfiguration(String fileName, Charset encoding) throws ConfigurationException { super(fileName, encoding); } public EncryptedConfiguration(File file) throws ConfigurationException { super(file); } public EncryptedConfiguration(File file, Charset encoding) throws ConfigurationException { super(file, encoding); } public EncryptedConfiguration(InputStream inStream) throws ConfigurationException { super(inStream); } public EncryptedConfiguration(InputStream inStream, Charset encoding) throws ConfigurationException { super(inStream, encoding); } public EncryptedConfiguration(Properties properties) { super(properties); } public EncryptedConfiguration use(String passphraseProperty) throws ConfigurationException { passphrase = readPassphrase(passphraseProperty); return this; } public void toDecrypt(String ... encryptedProperties) throws ConfigurationException { for (String p : encryptedProperties) { toDecrypt(p); } } public void toDecrypt(String property) throws ConfigurationException { if (passphrase == null) { log.debug("Passphrase is null. Skipping decrypt."); return; } if (!containsKey(property)) { log.debug("Can't decrypt missing property: {}", property); return; } String toDecrypt = getString(property); if (!toDecrypt.startsWith("$1$")) { // this is not an encrypted password, just return it log.debug("Value for {} is not an encrypted string", property); return; } // remove the magic string toDecrypt = toDecrypt.substring(3); try { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); // NOTE: we are creating a 64byte digest here, // but only the first 16 bytes are used as the iv String ivString = passphrase + passphrase; String iv = DigestUtils.sha256Hex(ivString); String passphraseDigest = DigestUtils.sha256Hex(passphrase); // FIXME: katello-password creates a 64 byte key, but somewhere // it gets truncated to 32 bytes, so we have to do that here. SecretKeySpec spec = new SecretKeySpec(Arrays.copyOfRange( passphraseDigest.getBytes(), 0, 32), "AES"); cipher.init(Cipher.DECRYPT_MODE, spec, new IvParameterSpec(iv.getBytes(), 0, 16)); // NOTE: the encrypted password is stored hex base64 byte[] b64bytes = Base64.decodeBase64(toDecrypt); String decrypted = new String(cipher.doFinal(b64bytes)); setProperty(property, decrypted); } catch (Exception e) { log.error("Failure trying to decrypt value of {}", property, e); throw new ConfigurationException(e); } } protected String readPassphrase(String passphraseProperty) throws ConfigurationException { if (!containsKey(passphraseProperty)) { log.info("No secret file provided."); return null; } String secretFile = getString(passphraseProperty); if (StringUtils.isEmpty(secretFile)) { log.warn("{} is present in the configuration but unset", passphraseProperty); return null; } log.debug("reading secret file: {}", secretFile); try { /* XXX Maybe it'd be better to use the charset the caller specifies during * construction? But just because the config is in that charset doesn't mean * the password file is. Stick with system default for now. */ InputStream bs = new FileInputStream(secretFile); return IOUtils.toString(bs, Charset.defaultCharset().name()); } catch (Exception e) { String msg = String.format("Could not read: %s", secretFile); log.error(msg); throw new ConfigurationException(msg, e); } } }