001package org.intellimate.izou.security; 002 003import org.apache.logging.log4j.LogManager; 004import org.apache.logging.log4j.Logger; 005import org.intellimate.izou.addon.AddOnManager; 006import org.intellimate.izou.addon.AddOnModel; 007import org.intellimate.izou.identification.IdentificationManager; 008import org.intellimate.izou.main.Main; 009import org.intellimate.izou.security.exceptions.IzouPermissionException; 010import org.intellimate.izou.security.storage.SecureStorage; 011import org.intellimate.izou.support.SystemMail; 012import ro.fortsoft.pf4j.IzouPluginClassLoader; 013 014import java.io.FileDescriptor; 015import java.security.Permission; 016import java.security.Security; 017import java.util.ArrayList; 018import java.util.List; 019import java.util.Optional; 020import java.util.function.BiConsumer; 021 022/** 023 * The IzouSecurityManager gives permission to all entitled components of Izou to execute or access files or commands. 024 * It also blocks access to all potentially insecure actions. 025 */ 026public final class SecurityManager extends java.lang.SecurityManager { 027 private static boolean exists = false; 028 private final SecureAccess secureAccess; 029 private final PermissionManager permissionManager; 030 private final SystemMail systemMail; 031 private final Main main; 032 //TODO Design: move to other class 033 private final List<String> forbiddenProperties; 034 private final List<String> forbiddenPackagesForAddOns; 035 036 /** 037 * Creates a SecurityManager. There can only be one single SecurityManager, so calling this method twice 038 * will cause an illegal access exception. 039 * 040 * @param systemMail the system mail object in order to send e-mails to owner in case of emergency 041 * @param main a reference to the main instance 042 * @return a SecurityManager from Izou 043 * @throws IllegalAccessException thrown if this method is called more than once 044 */ 045 public static SecurityManager createSecurityManager(SystemMail systemMail, Main main) throws IllegalAccessException { 046 if (!exists) { 047 SecurityManager securityManager = new SecurityManager(systemMail, main); 048 exists = true; 049 return securityManager; 050 } 051 throw new IllegalAccessException("Cannot create more than one instance of IzouSecurityManager"); 052 } 053 054 /** 055 * Creates a new IzouSecurityManager instance 056 * 057 * @param systemMail the system mail object in order to send e-mails to owner in case of emergency 058 * @param main the instance of main 059 */ 060 private SecurityManager(SystemMail systemMail, Main main) throws IllegalAccessException { 061 super(); 062 if (exists) { 063 throw new IllegalAccessException("Cannot create more than one instance of IzouSecurityManager"); 064 } 065 066 Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); 067 068 this.systemMail = systemMail; 069 this.main = main; 070 071 SecureStorage.createSecureStorage(main); 072 073 SecureAccess tempSecureAccess = null; 074 try { 075 tempSecureAccess = SecureAccess.createSecureAccess(main, systemMail); 076 } catch (IllegalAccessException e) { 077 Logger logger = LogManager.getLogger(this.getClass()); 078 logger.fatal("Unable to create a SecureAccess object because Izou might be under attack. " 079 + "Exiting now.", e); 080 getSecureAccess().doElevated(() -> System.exit(1)); 081 } 082 permissionManager = new PermissionManager(main, this); 083 secureAccess = tempSecureAccess; 084 forbiddenProperties = new ArrayList<>(); 085 forbiddenProperties.add("jdk.lang.process.launchmechanism"); 086 087 forbiddenPackagesForAddOns = new ArrayList<>(); 088 forbiddenPackagesForAddOns.add(SecurityManager.class.getPackage().getName()); 089 forbiddenPackagesForAddOns.add(IzouPermissionException.class.getPackage().getName()); 090 forbiddenPackagesForAddOns.add(AddOnManager.class.getPackage().getName()); 091 forbiddenPackagesForAddOns.add(IdentificationManager.class.getPackage().getName()); 092 } 093 094 SecureAccess getSecureAccess() { 095 return secureAccess; 096 } 097 098 PermissionManager getPermissionManager() { 099 return permissionManager; 100 } 101 102 /** 103 * Gets the current AddOnModel, that is the AddOnModel for the class loader to which the class belongs that 104 * triggered the security manager call, or throws a IzouPermissionException 105 * @return AddOnModel or IzouPermissionException if the call was made from an AddOn, or null if no AddOn is responsible 106 * @throws IzouPermissionException if the AddOnModel is not found 107 */ 108 public Optional<AddOnModel> getAddOnModelForClassLoader() { 109 Class[] classes = getClassContext(); 110 for (int i = classes.length - 1; i >= 0; i--) { 111 if (classes[i].getClassLoader() instanceof IzouPluginClassLoader && !classes[i].getName().toLowerCase() 112 .contains(IzouPluginClassLoader.PLUGIN_PACKAGE_PREFIX_IZOU_SDK)) { 113 ClassLoader classLoader = classes[i].getClassLoader(); 114 return main.getAddOnManager().getAddOnForClassLoader(classLoader); 115 } 116 } 117 return Optional.empty(); 118 } 119 120 /** 121 * Makes the {@link #getClassContext()} method of the security manager available to the entire package 122 * 123 * @return look at {@link #getClassContext()} 124 */ 125 Class[] getClassContextPkg() { 126 return getClassContext(); 127 } 128 129 /** 130 * this method first performs some basic checks and then performs the specific check 131 * @param t permission or file 132 * @param specific the specific check 133 */ 134 private <T> void check(T t, BiConsumer<T, AddOnModel> specific) { 135 if (!shouldCheck()) { 136 return; 137 } 138 secureAccess.doElevated(this::getAddOnModelForClassLoader) 139 .ifPresent(addOnModel -> 140 secureAccess.doElevated(() -> specific.accept(t, addOnModel))); 141 } 142 143 /** 144 * performs some basic checks to determine whether to check the permission 145 * @return true if should be checked, false if not 146 */ 147 // TODO: @leander, why does this just invert checkForSecureAccess? do we really need a method for this? 148 public boolean shouldCheck() { 149 return !checkForSecureAccess(); 150 } 151 152 /** 153 * Checks if {@link SecureAccess} is included in the current class context, if so true is returned, else false 154 * 155 * @return true if {@link SecureAccess} is included in the current class context, else false 156 */ 157 private boolean checkForSecureAccess() { 158 Class[] classContext = getClassContext(); 159 for (Class clazz : classContext) { 160 if (clazz.equals(SecureAccess.class) || clazz.equals(SecurityBreachHandler.class) 161 || clazz.equals(SecurityFunctions.class) || clazz.equals(SecureStorage.class)) { 162 return true; 163 } 164 } 165 return false; 166 } 167 168 /** 169 * Checks if addon components are in the stack (including sdk) 170 * 171 * @return true if addon components are in the stack (meaning they used the current application) 172 */ 173 private boolean checkForAddOnAccess() { 174 Class[] classContext = getClassContext(); 175 for (Class clazz : classContext) { 176 if (clazz.getClassLoader() instanceof IzouPluginClassLoader) { 177 return true; 178 } 179 } 180 return false; 181 } 182 183 /** 184 * Throws an exception with the argument of {@code argument} 185 * @param argument what the exception is about (Access denied to (argument goes here)) 186 */ 187 SecurityException getException(String argument) { 188 SecurityException exception = new SecurityException("Access denied to " + argument); 189 Class[] classStack = getClassContext(); 190 secureAccess.getBreachHandler().handleBreach(exception, classStack); 191 return exception; 192 } 193 194 @Override 195 public void checkPermission(Permission perm) { 196 check(perm, permissionManager::checkPermission); 197 } 198 199 @Override 200 public void checkPropertyAccess(String key) { 201 if (!shouldCheck()) { 202 return; 203 } 204 String canonicalKey = key.intern().toLowerCase(); 205 206 boolean allowedProperty = true; 207 for (String property : forbiddenProperties) { 208 if (canonicalKey.contains(property.toLowerCase())) { 209 allowedProperty = false; 210 break; 211 } 212 } 213 214 if (!allowedProperty) { 215 throw getException(key); 216 } 217 } 218 219 @Override 220 public void checkExec(String cmd) { 221 if (!shouldCheck()) { 222 return; 223 } 224 throw getException(cmd); 225 } 226 227 @Override 228 public void checkExit(int status) { 229 if (!checkForSecureAccess()) { 230 throw getException("exit"); 231 } 232 } 233 234 @Override 235 public void checkDelete(String file) { 236 if (!shouldCheck()) { 237 return; 238 } 239 } 240 241 @Override 242 public void checkAccess(ThreadGroup g) { 243 if (!shouldCheck()) { 244 return; 245 } 246 } 247 248 @Override 249 public void checkAccess(Thread t) { 250 if (!shouldCheck()) { 251 return; 252 } 253 } 254 255 @Override 256 public void checkRead(String file) { 257 if (file.endsWith("/org/intellimate/izou/security/SecurityModule.class")) 258 return; 259 if (!shouldCheck()) { 260 return; 261 } 262 if (!getAddOnModelForClassLoader().isPresent()) { 263 return; 264 } 265 permissionManager.getFilePermissionModule().fileReadCheck(file); 266 } 267 268 @Override 269 public void checkWrite(FileDescriptor fd) { 270 check(fd.toString(), permissionManager.getFilePermissionModule()::fileWriteCheck); 271 } 272 273 @Override 274 public void checkPackageAccess(String pkg) { 275 // Denies addOns access to packages in the list "forbiddenPackagesForAddOns" 276 if (forbiddenPackagesForAddOns.contains(pkg) && (!checkForSecureAccess() && checkForAddOnAccess())) { 277 throw getException("package: " + pkg); 278 } 279 } 280}