001package org.intellimate.izou.security.storage; 002 003import org.apache.logging.log4j.LogManager; 004import org.apache.logging.log4j.Logger; 005import org.intellimate.izou.main.Main; 006import org.intellimate.izou.security.SecurityFunctions; 007import org.intellimate.izou.util.IzouModule; 008import ro.fortsoft.pf4j.PluginDescriptor; 009 010import javax.crypto.SecretKey; 011import java.io.*; 012import java.security.KeyStore; 013import java.security.KeyStoreException; 014import java.security.NoSuchAlgorithmException; 015import java.security.UnrecoverableEntryException; 016import java.security.cert.CertificateException; 017import java.util.HashMap; 018 019/** 020 * The SecureStorage class offers a way for addOns to store data so that other addOns cannot access it. For example if 021 * addOn A wants to store the users username and password to some service, it can do so using this class without any 022 * other addOn having access to that information. 023 * <p> 024 * While the stored information is encrypted, it is not safe from the user. In theory and with a lot of effort, the 025 * user could extract the keys and decrypt the stored information since the keys are not hidden from the user (only 026 * from addOns). 027 * </p> 028 */ 029public final class SecureStorage extends IzouModule { 030 private static boolean exists = false; 031 private static SecureStorage secureStorage; 032 033 private HashMap<SecretKey, SecureContainer> containers; 034 private final Logger logger = LogManager.getLogger(this.getClass()); 035 036 /** 037 * Creates an SecureStorage. There can only be one single SecureStorage, so calling this method twice 038 * will cause an illegal access exception. 039 * 040 * @param main the main instance of izou 041 * @return a SecureAccess object 042 * @throws IllegalAccessException thrown if this method is called more than once 043 */ 044 public static SecureStorage createSecureStorage(Main main) throws IllegalAccessException { 045 if (!exists) { 046 SecureStorage secureStorage = new SecureStorage(main); 047 exists = true; 048 SecureStorage.secureStorage = secureStorage; 049 return secureStorage; 050 } 051 052 throw new IllegalAccessException("Cannot create more than one instance of IzouSecurityManager"); 053 } 054 055 /** 056 * Gets the instance of the secure storage object or null if it has not been created yet 057 * 058 * @return the instance of the secure storage object or null if it has not been created yet 059 */ 060 public synchronized static SecureStorage getInstance() { 061 if (exists) { 062 return SecureStorage.secureStorage; 063 } 064 065 return null; 066 } 067 068 /** 069 * Creates a new SecureStorage instance if and only if none has been created yet 070 * 071 * @param main the main instance of izou 072 * @throws IllegalAccessException thrown if this method is called more than once 073 */ 074 public SecureStorage(Main main) throws IllegalAccessException, NullPointerException { 075 super(main); 076 if (exists) { 077 throw new IllegalAccessException("Cannot create more than one instance of IzouSecurityManager"); 078 } 079 080 SecretKey key = retrieveKey(); 081 if (key == null) { 082 SecurityFunctions securityFunctions = new SecurityFunctions(); 083 key = securityFunctions.generateKey(); 084 085 if (key != null) { 086 storeKey(key); 087 } else { 088 throw new NullPointerException("Unable to create security key"); 089 } 090 } 091 092 containers = retrieveContainers(); 093 if (containers == null) { 094 containers = new HashMap<>(); 095 } 096 } 097 098 /** 099 * Stores a {@link SecureContainer} with the given secure ID of the plugin descriptor. Each addOn can only have 1 100 * secure container, so in order to update it, retrieve it and store it again. 101 * 102 * @param descriptor The plugin descriptor belonging to an addOn 103 * @param container The secure container to be stored with an addOn 104 */ 105 public void store(PluginDescriptor descriptor, SecureContainer container) { 106 HashMap<String, String> clearTextData = container.getClearTextData(); 107 HashMap<byte[], byte[]> cryptData = container.getCryptData(); 108 109 SecretKey secretKey = retrieveKey(); 110 SecurityFunctions module = new SecurityFunctions(); 111 for (byte[] key : cryptData.keySet()) { 112 clearTextData.put(module.decryptAES(key, secretKey), module.decryptAES(cryptData.get(key), secretKey)); 113 cryptData.remove(key); 114 } 115 116 container.setCryptData(cryptData); 117 containers.put(descriptor.getSecureID(), container); 118 saveContainers(); 119 } 120 121 /** 122 * Retrieves a {@link SecureContainer} with the given secure ID of the plugin descriptor 123 * 124 * @param descriptor The plugin descriptor belonging to an addOn 125 * @return container The secure container that was retrieved 126 */ 127 public SecureContainer retrieve(PluginDescriptor descriptor) { 128 SecureContainer container = containers.get(descriptor.getSecureID()); 129 HashMap<byte[], byte[]> cryptData = container.getCryptData(); 130 HashMap<String, String> clearTextData = container.getClearTextData(); 131 132 SecretKey secretKey = retrieveKey(); 133 SecurityFunctions module = new SecurityFunctions(); 134 for (byte[] key : cryptData.keySet()) { 135 clearTextData.put(module.decryptAES(key, secretKey), module.decryptAES(cryptData.get(key), secretKey)); 136 cryptData.remove(key); 137 } 138 139 container.setClearTextData(clearTextData); 140 return container; 141 } 142 143 /** 144 * Saves the containers to ./system/data/containers.ser 145 */ 146 private void saveContainers() { 147 String workingDir = getMain().getFileSystemManager().getSystemDataLocation().getAbsolutePath(); 148 final String containerFile = workingDir + File.separator + "containers.ser"; 149 try { 150 FileOutputStream fileOut = new FileOutputStream(containerFile); 151 ObjectOutputStream out = new ObjectOutputStream(fileOut); 152 out.writeObject(containers); 153 out.close(); 154 fileOut.close(); 155 } catch(IOException e) { 156 logger.error("Unable to save containers to file", e); 157 } 158 } 159 160 /** 161 * Retrieves the containers from ./system/data/containers.ser if the file is found, else returns null 162 * 163 * @return the containers from ./system/data/containers.ser if the file is found, else null 164 */ 165 private HashMap<SecretKey, SecureContainer> retrieveContainers() { 166 HashMap<SecretKey, SecureContainer> containers = null; 167 String workingDir = getMain().getFileSystemManager().getSystemDataLocation().getAbsolutePath(); 168 final String containerFile = workingDir + File.separator 169 + "containers.ser"; 170 try { 171 FileInputStream fileIn = new FileInputStream(containerFile); 172 ObjectInputStream in = new ObjectInputStream(fileIn); 173 Object o = in.readObject(); 174 if (o instanceof HashMap) { 175 containers = (HashMap) o; 176 } 177 in.close(); 178 fileIn.close(); 179 } catch (FileNotFoundException e) { 180 return null; 181 } catch(IOException | ClassNotFoundException e) { 182 logger.error("Unable to retrieve containers from file", e); 183 } 184 185 return containers; 186 } 187 188 /** 189 * Retrieves the izou aes key stored in a keystore 190 * 191 * @return the izou aes key stored in a keystore 192 */ 193 private SecretKey retrieveKey() { 194 SecretKey key = null; 195 try { 196 String workingDir = getMain().getFileSystemManager().getSystemLocation().getAbsolutePath(); 197 final String keyStoreFile = workingDir + File.separator + "izou.keystore"; 198 KeyStore keyStore = createKeyStore(keyStoreFile, "4b[X:+H4CS&avY<)"); 199 200 KeyStore.PasswordProtection keyPassword = new KeyStore.PasswordProtection("Ev45j>eP}QTR?K9_".toCharArray()); 201 KeyStore.Entry entry = keyStore.getEntry("izou_key", keyPassword); 202 key = ((KeyStore.SecretKeyEntry) entry).getSecretKey(); 203 } catch(NullPointerException e) { 204 return null; 205 } catch (UnrecoverableEntryException | NoSuchAlgorithmException | KeyStoreException e) { 206 logger.error("Unable to retrieve key", e); 207 } 208 209 return key; 210 } 211 212 /** 213 * Stores the izou aes key in a keystore 214 * 215 * @param key the key to store 216 */ 217 private void storeKey(SecretKey key) { 218 final String keyStoreFile = getMain().getFileSystemManager().getSystemLocation() + File.separator + "izou.keystore"; 219 KeyStore keyStore = createKeyStore(keyStoreFile, "4b[X:+H4CS&avY<)"); 220 221 try { 222 KeyStore.SecretKeyEntry keyStoreEntry = new KeyStore.SecretKeyEntry(key); 223 KeyStore.PasswordProtection keyPassword = new KeyStore.PasswordProtection("Ev45j>eP}QTR?K9_".toCharArray()); 224 keyStore.setEntry("izou_key", keyStoreEntry, keyPassword); 225 keyStore.store(new FileOutputStream(keyStoreFile), "4b[X:+H4CS&avY<)".toCharArray()); 226 } catch (NoSuchAlgorithmException | KeyStoreException 227 | CertificateException | IOException e) { 228 logger.error("Unable to store key", e); 229 } 230 } 231 232 /** 233 * Creates a new keystore for the izou aes key 234 * 235 * @param fileName the path to the keystore 236 * @param password the password to use with the keystore 237 * @return the newly created keystore 238 */ 239 private KeyStore createKeyStore(String fileName, String password) { 240 File file = new File(fileName); 241 KeyStore keyStore = null; 242 try { 243 keyStore = KeyStore.getInstance("JCEKS"); 244 if (file.exists()) { 245 keyStore.load(new FileInputStream(file), password.toCharArray()); 246 } else { 247 keyStore.load(null, null); 248 keyStore.store(new FileOutputStream(fileName), password.toCharArray()); 249 } 250 } catch (CertificateException | IOException | KeyStoreException | NoSuchAlgorithmException e) { 251 logger.error("Unable to create key store", e); 252 } 253 254 return keyStore; 255 } 256}