package org.intellimate.izou.security.storage; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.intellimate.izou.main.Main; import org.intellimate.izou.security.SecurityFunctions; import org.intellimate.izou.util.IzouModule; import ro.fortsoft.pf4j.PluginDescriptor; import javax.crypto.SecretKey; import java.io.*; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableEntryException; import java.security.cert.CertificateException; import java.util.HashMap; /** * The SecureStorage class offers a way for addOns to store data so that other addOns cannot access it. For example if * addOn A wants to store the users username and password to some service, it can do so using this class without any * other addOn having access to that information. * <p> * While the stored information is encrypted, it is not safe from the user. In theory and with a lot of effort, the * user could extract the keys and decrypt the stored information since the keys are not hidden from the user (only * from addOns). * </p> */ public final class SecureStorageImpl extends IzouModule implements SecureStorage { private static boolean exists = false; private static SecureStorage secureStorage; private HashMap<SecretKey, SecureContainer> containers; private final Logger logger = LogManager.getLogger(this.getClass()); /** * Creates an SecureStorage. There can only be one single SecureStorage, so calling this method twice * will cause an illegal access exception. * * @param main the main instance of izou * @return a SecureAccess object * @throws IllegalAccessException thrown if this method is called more than once */ public static SecureStorage createSecureStorage(Main main) throws IllegalAccessException { if (!exists) { SecureStorage secureStorage = new SecureStorageImpl(main); exists = true; SecureStorageImpl.secureStorage = secureStorage; return secureStorage; } throw new IllegalAccessException("Cannot create more than one instance of IzouSecurityManager"); } /** * Gets the instance of the secure storage object or null if it has not been created yet * * @return the instance of the secure storage object or null if it has not been created yet */ public synchronized static SecureStorage getInstance() { if (exists) { return SecureStorageImpl.secureStorage; } return null; } /** * Creates a new SecureStorage instance if and only if none has been created yet * * @param main the main instance of izou * @throws IllegalAccessException thrown if this method is called more than once */ public SecureStorageImpl(Main main) throws IllegalAccessException, NullPointerException { super(main); if (exists) { throw new IllegalAccessException("Cannot create more than one instance of IzouSecurityManager"); } SecretKey key = retrieveKey(); if (key == null) { SecurityFunctions securityFunctions = new SecurityFunctions(); key = securityFunctions.generateKey(); if (key != null) { storeKey(key); } else { throw new NullPointerException("Unable to create security key"); } } containers = retrieveContainers(); if (containers == null) { containers = new HashMap<>(); } } /** * Stores a {@link SecureContainer} with the given secure ID of the plugin descriptor. Each addOn can only have 1 * secure container, so in order to update it, retrieve it and store it again. * * @param descriptor The plugin descriptor belonging to an addOn * @param container The secure container to be stored with an addOn */ @Override public void store(PluginDescriptor descriptor, SecureContainer container) { HashMap<String, String> clearTextData = container.getClearTextData(); HashMap<byte[], byte[]> cryptData = container.getCryptData(); SecretKey secretKey = retrieveKey(); SecurityFunctions module = new SecurityFunctions(); for (byte[] key : cryptData.keySet()) { clearTextData.put(module.decryptAES(key, secretKey), module.decryptAES(cryptData.get(key), secretKey)); cryptData.remove(key); } container.setCryptData(cryptData); containers.put(descriptor.getSecureID(), container); saveContainers(); } /** * Retrieves a {@link SecureContainer} with the given secure ID of the plugin descriptor * * @param descriptor The plugin descriptor belonging to an addOn * @return container The secure container that was retrieved */ @Override public SecureContainer retrieve(PluginDescriptor descriptor) { SecureContainer container = containers.get(descriptor.getSecureID()); HashMap<byte[], byte[]> cryptData = container.getCryptData(); HashMap<String, String> clearTextData = container.getClearTextData(); SecretKey secretKey = retrieveKey(); SecurityFunctions module = new SecurityFunctions(); for (byte[] key : cryptData.keySet()) { clearTextData.put(module.decryptAES(key, secretKey), module.decryptAES(cryptData.get(key), secretKey)); cryptData.remove(key); } container.setClearTextData(clearTextData); return container; } /** * Saves the containers to ./system/data/containers.ser */ private void saveContainers() { String workingDir = getMain().getFileSystemManager().getSystemDataLocation().getAbsolutePath(); final String containerFile = workingDir + File.separator + "containers.ser"; try { FileOutputStream fileOut = new FileOutputStream(containerFile); ObjectOutputStream out = new ObjectOutputStream(fileOut); out.writeObject(containers); out.close(); fileOut.close(); } catch(IOException e) { logger.error("Unable to save containers to file", e); } } /** * Retrieves the containers from ./system/data/containers.ser if the file is found, else returns null * * @return the containers from ./system/data/containers.ser if the file is found, else null */ private HashMap<SecretKey, SecureContainer> retrieveContainers() { HashMap<SecretKey, SecureContainer> containers = null; String workingDir = getMain().getFileSystemManager().getSystemDataLocation().getAbsolutePath(); final String containerFile = workingDir + File.separator + "containers.ser"; try { FileInputStream fileIn = new FileInputStream(containerFile); ObjectInputStream in = new ObjectInputStream(fileIn); Object o = in.readObject(); if (o instanceof HashMap) { containers = (HashMap) o; } in.close(); fileIn.close(); } catch (FileNotFoundException e) { return null; } catch(IOException | ClassNotFoundException e) { logger.error("Unable to retrieve containers from file", e); } return containers; } /** * Retrieves the izou aes key stored in a keystore * * @return the izou aes key stored in a keystore */ private SecretKey retrieveKey() { SecretKey key = null; try { String workingDir = getMain().getFileSystemManager().getSystemLocation().getAbsolutePath(); final String keyStoreFile = workingDir + File.separator + "izou.keystore"; KeyStore keyStore = createKeyStore(keyStoreFile, "4b[X:+H4CS&avY<)"); KeyStore.PasswordProtection keyPassword = new KeyStore.PasswordProtection("Ev45j>eP}QTR?K9_".toCharArray()); KeyStore.Entry entry = keyStore.getEntry("izou_key", keyPassword); key = ((KeyStore.SecretKeyEntry) entry).getSecretKey(); } catch(NullPointerException e) { return null; } catch (UnrecoverableEntryException | NoSuchAlgorithmException | KeyStoreException e) { logger.error("Unable to retrieve key", e); } return key; } /** * Stores the izou aes key in a keystore * * @param key the key to store */ private void storeKey(SecretKey key) { final String keyStoreFile = getMain().getFileSystemManager().getSystemLocation() + File.separator + "izou.keystore"; KeyStore keyStore = createKeyStore(keyStoreFile, "4b[X:+H4CS&avY<)"); try { KeyStore.SecretKeyEntry keyStoreEntry = new KeyStore.SecretKeyEntry(key); KeyStore.PasswordProtection keyPassword = new KeyStore.PasswordProtection("Ev45j>eP}QTR?K9_".toCharArray()); keyStore.setEntry("izou_key", keyStoreEntry, keyPassword); keyStore.store(new FileOutputStream(keyStoreFile), "4b[X:+H4CS&avY<)".toCharArray()); } catch (NoSuchAlgorithmException | KeyStoreException | CertificateException | IOException e) { logger.error("Unable to store key", e); } } /** * Creates a new keystore for the izou aes key * * @param fileName the path to the keystore * @param password the password to use with the keystore * @return the newly created keystore */ private KeyStore createKeyStore(String fileName, String password) { File file = new File(fileName); KeyStore keyStore = null; try { keyStore = KeyStore.getInstance("JCEKS"); if (file.exists()) { keyStore.load(new FileInputStream(file), password.toCharArray()); } else { keyStore.load(null, null); keyStore.store(new FileOutputStream(fileName), password.toCharArray()); } } catch (CertificateException | IOException | KeyStoreException | NoSuchAlgorithmException e) { logger.error("Unable to create key store", e); } return keyStore; } }