package com.robonobo.wang.client; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.robonobo.wang.proto.WangProtocol.CoinMsg; /** * Stores coins securely on the filesystem. If there are no public methods * currently being executed, the file store is guaranteed to be in a recoverable * state. */ public class CoinStore { private Log log = LogFactory.getLog(getClass()); private File storageDir; private Cipher encryptCipher; private Cipher decryptCipher; private Map<Integer, List<String>> coinIds = new HashMap<Integer, List<String>>(); public CoinStore(File storageDir, String password) { // Create our encryption ciphers try { byte[] keyArr = generateKey(password, 16); SecretKeySpec sekritKey = new SecretKeySpec(keyArr, "AES"); encryptCipher = Cipher.getInstance("AES"); encryptCipher.init(Cipher.ENCRYPT_MODE, sekritKey); decryptCipher = Cipher.getInstance("AES"); decryptCipher.init(Cipher.DECRYPT_MODE, sekritKey); } catch (Exception e) { throw new RuntimeException(e); } // Load coins from dir this.storageDir = storageDir; if(!storageDir.exists()) storageDir.mkdirs(); log.info("CoinStore loading persisted coins from "+storageDir.getAbsolutePath()); double coinVal = 0; int numCoins = 0; for (File coinFile : storageDir.listFiles()) { try { CoinMsg coin = loadCoinFromFile(coinFile.getName()); if(!coinIds.containsKey(coin.getDenom())) coinIds.put(coin.getDenom(), new LinkedList<String>()); coinIds.get(coin.getDenom()).add(coin.getCoinId().toString()); coinVal += getDenomValue(coin.getDenom()); numCoins++; } catch (Exception e) { log.error("CoinStore encountered error ("+e.getClass().getName()+") loading coin. Skipping."); } } log.info("CoinStore loaded "+numCoins+" coins, value="+coinVal); } public synchronized int numCoins(int denom) { return (coinIds.containsKey(denom)) ? coinIds.get(denom).size() : 0; } public synchronized CoinMsg getCoin(int denom) throws CoinStoreException { if(!coinIds.containsKey(denom) || coinIds.get(denom).size() == 0) return null; String coinId = coinIds.get(denom).remove(0); CoinMsg coin; try { coin = loadCoinFromFile(coinId); } catch (Exception e) { throw new CoinStoreException(e); } new File(storageDir, coinId).delete(); return coin; } public synchronized void putCoin(CoinMsg coin) throws CoinStoreException { if(!coinIds.containsKey(coin.getDenom())) coinIds.put(coin.getDenom(), new LinkedList<String>()); coinIds.get(coin.getDenom()).add(coin.getCoinId().toString()); try { saveCoinToFile(coin); } catch (Exception e) { throw new CoinStoreException(e); } } private void saveCoinToFile(CoinMsg coin) throws IOException, GeneralSecurityException { byte[] encArr = encryptCipher.doFinal(coin.toByteArray()); File file = new File(storageDir, coin.getCoinId().toString()); FileOutputStream fos = new FileOutputStream(file); fos.write(encArr); fos.close(); } private CoinMsg loadCoinFromFile(String coinId) throws IOException, GeneralSecurityException { File file = new File(storageDir, coinId); FileInputStream fis = new FileInputStream(file); byte[] readArr = new byte[1024]; ByteArrayOutputStream baos = new ByteArrayOutputStream(); int numRead; while((numRead = fis.read(readArr)) > 0) { baos.write(readArr, 0, numRead); } fis.close(); byte[] plainArr = decryptCipher.doFinal(baos.toByteArray()); return CoinMsg.parseFrom(plainArr); } private byte[] generateKey(String password, int keyLength) { MessageDigest m; try { m = MessageDigest.getInstance("sha-256"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(); } m.update(password.getBytes()); byte[] key = new byte[keyLength]; System.arraycopy(m.digest(), 0, key, 0, keyLength); return key; } private double getDenomValue(Integer denom) { return Math.pow(2, denom); } }