/* * Copyright (c) 2012 EMC Corporation * All Rights Reserved */ package com.emc.storageos.db.client.impl; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.coordinator.client.service.CoordinatorClient; import com.emc.storageos.coordinator.common.Configuration; import com.emc.storageos.coordinator.common.impl.ConfigurationImpl; import com.emc.storageos.db.client.model.EncryptionProvider; import org.apache.curator.framework.recipes.locks.InterProcessLock; /** * Default encryption provider. Uses AES encryption. */ public class EncryptionProviderImpl implements EncryptionProvider { private static final Logger _logger = LoggerFactory.getLogger(EncryptionProviderImpl.class); private static final String CONFIG_KIND = "encryption"; private static final String CONFIG_ID = "id"; private static final String ALGO = "AES"; private static Charset UTF_8 = Charset.forName("UTF-8"); private static final byte ENC_PROVIDER_VERSION = 0x01; private CoordinatorClient _coordinator; private SecretKey _key; private Cipher _cipher; private Cipher _decipher; private String _encryptId = CONFIG_ID; /** * @param configId the configId to set */ public void setEncryptId(String encryptId) { this._encryptId = encryptId; } public void setCoordinator(CoordinatorClient coordinator) { _coordinator = coordinator; } public SecretKey getKey() { return this._key; } @Override public void start() { try { cacheKey(); _cipher = Cipher.getInstance(ALGO); _cipher.init(Cipher.ENCRYPT_MODE, _key); _decipher = Cipher.getInstance(ALGO); _decipher.init(Cipher.DECRYPT_MODE, _key); } catch (Exception e) { throw new IllegalStateException(e); } } /** * Reads existing encryption key from coordinator and caches it * * @throws Exception */ private synchronized void cacheKey() throws Exception { Configuration config = _coordinator.queryConfiguration(CONFIG_KIND, _encryptId); if (config != null) { readKey(config); } else { // key has not been generated InterProcessLock lock = null; try { lock = _coordinator.getLock(CONFIG_KIND); lock.acquire(); config = _coordinator.queryConfiguration(CONFIG_KIND, _encryptId); if (config == null) { _logger.warn("Encryption key not found, initializing it"); generateKey(); } else { readKey(config); } } finally { if (lock != null) { lock.release(); } } } } /** * Generates a new key and persists it to coordinator * * @throws Exception */ private void generateKey() throws Exception { KeyGenerator keygen = KeyGenerator.getInstance(ALGO); SecretKey key = keygen.generateKey(); persistKey(key); } /** * Persists key to coordinator * * @throws Exception */ private void persistKey(SecretKey key) throws Exception { ConfigurationImpl config = new ConfigurationImpl(); config.setKind(CONFIG_KIND); config.setId(_encryptId); config.setConfig(CONFIG_KIND, new String(Base64.encodeBase64(key.getEncoded()), UTF_8)); _coordinator.persistServiceConfiguration(config); _key = key; } /** * Deserialize secret key * * @param config */ private void readKey(Configuration config) { String base64Encoded = config.getConfig(CONFIG_KIND); SecretKey key = new SecretKeySpec(Base64.decodeBase64( base64Encoded.getBytes(UTF_8)), ALGO); _key = key; } // Add a version number private byte[] encode(byte[] input) { byte[] out = new byte[input.length + 1]; out[0] = ENC_PROVIDER_VERSION; System.arraycopy(input, 0, out, 1, input.length); return out; } // remove the version number private byte[] decode(byte[] input) { if (input.length < 2) { throw new IllegalStateException("decrypt decode failed from db, invalid input (length < 2)"); } else if (input[0] != ENC_PROVIDER_VERSION) { throw new IllegalStateException("decrypt decode failed from db: " + "version found: " + input[0] + "version expected: " + ENC_PROVIDER_VERSION); } byte[] out = new byte[input.length - 1]; System.arraycopy(input, 1, out, 0, input.length - 1); return out; } @Override public byte[] encrypt(String input) { try { return encode(_cipher.doFinal(input.getBytes(UTF_8))); } catch (Exception e) { throw new IllegalStateException(e); } } @Override public String decrypt(byte[] input) { try { byte[] enc = decode(input); return new String(_decipher.doFinal(enc), UTF_8); } catch (Exception e) { throw new IllegalStateException(e); } } @Override public String getEncryptedString(String input) { byte[] data = encrypt(input); try { return new String(Base64.encodeBase64(data), "UTF-8"); } catch (UnsupportedEncodingException e) { // All JVMs must support UTF-8, this really can never happen throw new RuntimeException(e); } } public void restoreKey(SecretKey key) throws Exception { InterProcessLock lock = null; try { lock = _coordinator.getLock(CONFIG_KIND); lock.acquire(); persistKey(key); } finally { if (lock != null) { lock.release(); } } } }