/* * Copyright 2014 individual contributors as indicated by the @author * tags * * This is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This software 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 General Public License * along with this software. If not, see <http://www.gnu.org/licenses/>. */ package org.sector67.otp.key; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Properties; import java.util.Random; import java.util.Set; import org.sector67.otp.utils.FileUtils; /** * A file-based implementation of an OTP keystore, primarily for testing * purposes. Uses a properties file to store the key offsets. * * @author scott.hasse@gmail.com * */ public class FileKeyStore implements TestableKeyStore { String OFFSET_FILE_NAME = "offsets.properties"; String keyDirectory; Random r = new SecureRandom(); private KeyEraser eraser = new MultiPassSecureRandomEraser(); public FileKeyStore(String keyDirectory) { this.keyDirectory = keyDirectory; } @Override public void init() throws KeyException { File keyDir = new File(keyDirectory); if (!keyDir.exists()) { boolean result = keyDir.mkdirs(); if (result == false) { throw new KeyException( "The configured key directory does not exist, and could not be created: [" + keyDirectory + "]"); } } if (!keyDir.isDirectory()) { throw new KeyException( "The configured key directory is not a directory: " + keyDirectory); } File offsetFile = new File(keyDirectory + File.separator + OFFSET_FILE_NAME); if (!offsetFile.exists()) { Properties p = new Properties(); writeOffsetFile(p); } } @Override public void setKeyEraser(KeyEraser eraser) { this.eraser = eraser; } /* * Provides the next bytes from the key and replaces those bytes with random * information updates the current index of the key */ @Override public byte[] getKeyBytesForEncryption(String name, int length) throws KeyException { if (name == null) { throw new KeyException("You cannot use a null key name."); } Properties p = readOffsetFile(); if (!p.containsKey(name)) { throw new KeyException( "The requested key does not exist in this key store: " + name); } int offset = Integer.parseInt(p.getProperty(name)); byte[] key = new byte[length]; try (RandomAccessFile file = new RandomAccessFile(keyDirectory + File.separator + name, "r");) { if (file.length() < offset + length) { throw new KeyException( "The key is not long enough to provide the requested bytes"); } file.seek(offset); file.read(key); } catch (IOException e) { throw new KeyException(e); } p = readOffsetFile(); p.setProperty(name, new Integer(offset + length).toString()); writeOffsetFile(p); return key; } @Override public Set<String> getKeyNames() throws KeyException { Properties p = readOffsetFile(); return p.stringPropertyNames(); } @Override public void copyKey(String source, String destination) throws KeyException { File input = new File(keyDirectory + File.separator + source); File output = new File(keyDirectory + File.separator + destination); try { copy(input, output); } catch (IOException e) { throw new KeyException(e); } Properties p = readOffsetFile(); p.setProperty(destination, p.getProperty(source)); writeOffsetFile(p); } @Override public void addKey(String name, byte[] key, int offset) throws KeyException { if (name == null) { throw new IllegalArgumentException( "You cannot create a null key name."); } Properties p = readOffsetFile(); if (p.containsKey(name)) { throw new IllegalArgumentException( "You cannot create a key that already exists: " + name); } try { FileUtils.write(key, keyDirectory + File.separator + name); } catch (IOException e) { throw new KeyException(e); } p = readOffsetFile(); p.setProperty(name, new Integer(offset).toString()); writeOffsetFile(p); } @Override public void deleteKey(String name) throws KeyException { Properties p = readOffsetFile(); if (p.containsKey(name)) { // TODO: wipe file using the appropriate strategy FileUtils.deleteFile(keyDirectory + File.separator + name); p.remove(name); writeOffsetFile(p); } } @Override public List<String> listKeys() throws KeyException { Properties p = readOffsetFile(); List<String> result = new ArrayList<String>(); Set<Object> keys = p.keySet(); for (Iterator<Object> iterator = keys.iterator(); iterator.hasNext();) { String keyName = (String) iterator.next(); result.add(keyName); } Collections.sort(result); return result; } @Override public void generateKey(String name, int length) throws KeyException { if (name == null) { throw new IllegalArgumentException( "You cannot create a null key name."); } Properties p = readOffsetFile(); if (p.containsKey(name)) { throw new IllegalArgumentException( "You cannot create a key that already exists: " + name); } byte[] b = new byte[length]; r.nextBytes(b); try { FileUtils.write(b, keyDirectory + File.separator + name); } catch (IOException e) { throw new KeyException(e); } p = readOffsetFile(); p.setProperty(name, "0"); writeOffsetFile(p); } /* * Destroys this keystore, eliminating all file-based key and index data, * and attempts to delete the keystore directory */ public void destroy() throws KeyException { // TODO: make this use the KeyEraser to clear data before deleting Properties p = readOffsetFile(); Set<String> names = p.stringPropertyNames(); for (String name : names) { FileUtils.deleteFile(keyDirectory + File.separator + name); } FileUtils.deleteFile(keyDirectory + File.separator + OFFSET_FILE_NAME); FileUtils.deleteFile(keyDirectory); } private void writeOffsetFile(Properties props) { try { File offsetFile = new File(keyDirectory + File.separator + OFFSET_FILE_NAME); OutputStream out = new FileOutputStream(offsetFile); props.store(out, "A property file storing the key offsets"); } catch (Exception e) { e.printStackTrace(); } } private Properties readOffsetFile() throws KeyException { Properties props = new Properties(); InputStream is = null; try { File offsetFile = new File(keyDirectory + File.separator + OFFSET_FILE_NAME); is = new FileInputStream(offsetFile); props.load(is); is.close(); } catch (IOException e) { throw new KeyException(e); } return props; } public class FileKeyData implements KeyData { private RandomAccessFile key; public FileKeyData(RandomAccessFile key) { this.key = key; } public void close() throws KeyException { try { key.close(); } catch (IOException e) { throw new KeyException(e); } } public void seek(int position) throws KeyException { try { key.seek(position); } catch (IOException e) { throw new KeyException(e); } } public void write(byte[] data) throws KeyException { try { key.write(data); } catch (IOException e) { throw new KeyException(e); } } } private void copy(File src, File dst) throws IOException { InputStream in = new FileInputStream(src); OutputStream out = new FileOutputStream(dst); // Transfer bytes from in to out byte[] buf = new byte[1024]; int len; while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); } in.close(); out.close(); } @Override public int getCurrentOffset(String keyName) throws KeyException { int offset = 0; Properties p = readOffsetFile(); if (p.containsKey(keyName)) { String offsetString = p.getProperty(keyName); try { offset = Integer.parseInt(offsetString); } catch (NumberFormatException e) { throw new KeyException(e); } } else { throw new KeyException("The key does not exist: [" + keyName + "]"); } return offset; } @Override public int getSize(String keyName) throws KeyException { // TODO Auto-generated method stub return 0; } @Override public byte[] getKeyBytesForDecryption(String name, int offset, int length) throws KeyException { if (name == null) { throw new KeyException("You cannot use a null key name."); } Properties p = readOffsetFile(); if (!p.containsKey(name)) { throw new KeyException( "The requested key does not exist in this key store: " + name); } byte[] key = new byte[length]; try (RandomAccessFile file = new RandomAccessFile(keyDirectory + File.separator + name, "r");) { if (file.length() < offset + length) { throw new KeyException( "The key is not long enough to provide the requested bytes"); } file.seek(offset); file.read(key); } catch (IOException e) { throw new KeyException(e); } return key; } public void eraseKeyBytes(String keyName, int pos, int length) throws KeyException { if (keyName == null) { throw new KeyException("You cannot use a null key name."); } RandomAccessFile key; try { key = new RandomAccessFile(keyDirectory + File.separator + keyName, "rw"); KeyData kd = new FileKeyData(key); eraser.erase(kd, pos, length); } catch (FileNotFoundException e) { throw new KeyException(e); } } }