package org.intellimate.izou.security; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.intellimate.izou.addon.AddOnManager; import org.intellimate.izou.addon.AddOnModel; import org.intellimate.izou.identification.IdentificationManagerM; import org.intellimate.izou.main.Main; import org.intellimate.izou.security.exceptions.IzouPermissionException; import org.intellimate.izou.security.storage.SecureStorageImpl; import org.intellimate.izou.support.SystemMail; import ro.fortsoft.pf4j.IzouPluginClassLoader; import java.io.FileDescriptor; import java.security.Permission; import java.security.Security; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.function.BiConsumer; /** * The IzouSecurityManager gives permission to all entitled components of Izou to execute or access files or commands. * It also blocks access to all potentially insecure actions. */ public final class SecurityManager extends java.lang.SecurityManager { private static boolean exists = false; private final SecureAccess secureAccess; private final PermissionManager permissionManager; private final SystemMail systemMail; private final Main main; //TODO Design: move to other class private final List<String> forbiddenProperties; private final List<String> forbiddenPackagesForAddOns; /** * Creates a SecurityManager. There can only be one single SecurityManager, so calling this method twice * will cause an illegal access exception. * * @param systemMail the system mail object in order to send e-mails to owner in case of emergency * @param main a reference to the main instance * @return a SecurityManager from Izou * @throws IllegalAccessException thrown if this method is called more than once */ public static SecurityManager createSecurityManager(SystemMail systemMail, Main main) throws IllegalAccessException { if (!exists) { SecurityManager securityManager = new SecurityManager(systemMail, main); exists = true; return securityManager; } throw new IllegalAccessException("Cannot create more than one instance of IzouSecurityManager"); } /** * Creates a new IzouSecurityManager instance * * @param systemMail the system mail object in order to send e-mails to owner in case of emergency * @param main the instance of main */ private SecurityManager(SystemMail systemMail, Main main) throws IllegalAccessException { super(); if (exists) { throw new IllegalAccessException("Cannot create more than one instance of IzouSecurityManager"); } Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); this.systemMail = systemMail; this.main = main; SecureStorageImpl.createSecureStorage(main); SecureAccess tempSecureAccess = null; try { tempSecureAccess = SecureAccess.createSecureAccess(main, systemMail); } catch (IllegalAccessException e) { Logger logger = LogManager.getLogger(this.getClass()); logger.fatal("Unable to create a SecureAccess object because Izou might be under attack. " + "Exiting now.", e); getSecureAccess().doElevated(() -> System.exit(1)); } permissionManager = new PermissionManager(main, this); secureAccess = tempSecureAccess; forbiddenProperties = new ArrayList<>(); forbiddenProperties.add("jdk.lang.process.launchmechanism"); forbiddenPackagesForAddOns = new ArrayList<>(); forbiddenPackagesForAddOns.add(SecurityManager.class.getPackage().getName()); forbiddenPackagesForAddOns.add(IzouPermissionException.class.getPackage().getName()); forbiddenPackagesForAddOns.add(AddOnManager.class.getPackage().getName()); forbiddenPackagesForAddOns.add(IdentificationManagerM.class.getPackage().getName()); } SecureAccess getSecureAccess() { return secureAccess; } PermissionManager getPermissionManager() { return permissionManager; } /** * Gets the current AddOnModel, that is the AddOnModel for the class loader to which the class belongs that * triggered the security manager call, or throws a IzouPermissionException * @return AddOnModel or IzouPermissionException if the call was made from an AddOn, or null if no AddOn is responsible * @throws IzouPermissionException if the AddOnModel is not found */ public Optional<AddOnModel> getAddOnModelForClassLoader() { Class[] classes = getClassContext(); for (int i = classes.length - 1; i >= 0; i--) { if (classes[i].getClassLoader() instanceof IzouPluginClassLoader && !classes[i].getName().toLowerCase() .contains(IzouPluginClassLoader.PLUGIN_PACKAGE_PREFIX_IZOU_SDK)) { ClassLoader classLoader = classes[i].getClassLoader(); return main.getAddOnInformationManager().getAddOnForClassLoader(classLoader); } } return Optional.empty(); } /** * Makes the {@link #getClassContext()} method of the security manager available to the entire package * * @return look at {@link #getClassContext()} */ Class[] getClassContextPkg() { return getClassContext(); } /** * this method first performs some basic checks and then performs the specific check * @param t permission or file * @param specific the specific check */ private <T> void check(T t, BiConsumer<T, AddOnModel> specific) { if (!shouldCheck()) { return; } secureAccess.doElevated(this::getAddOnModelForClassLoader) .ifPresent(addOnModel -> secureAccess.doElevated(() -> specific.accept(t, addOnModel))); } /** * performs some basic checks to determine whether to check the permission * @return true if should be checked, false if not */ // TODO: @leander, why does this just invert checkForSecureAccess? do we really need a method for this? public boolean shouldCheck() { return !checkForSecureAccess(); } /** * Checks if {@link SecureAccess} is included in the current class context, if so true is returned, else false * * @return true if {@link SecureAccess} is included in the current class context, else false */ private boolean checkForSecureAccess() { Class[] classContext = getClassContext(); for (Class clazz : classContext) { if (clazz.equals(SecureAccess.class) || clazz.equals(SecurityBreachHandler.class) || clazz.equals(SecurityFunctions.class) || clazz.equals(SecureStorageImpl.class)) { return true; } } return false; } /** * Checks if addon components are in the stack (including sdk) * * @return true if addon components are in the stack (meaning they used the current application) */ private boolean checkForAddOnAccess() { Class[] classContext = getClassContext(); for (Class clazz : classContext) { if (clazz.getClassLoader() instanceof IzouPluginClassLoader) { return true; } } return false; } /** * Throws an exception with the argument of {@code argument} * @param argument what the exception is about (Access denied to (argument goes here)) */ SecurityException getException(String argument) { SecurityException exception = new SecurityException("Access denied to " + argument); Class[] classStack = getClassContext(); secureAccess.getBreachHandler().handleBreach(exception, classStack); return exception; } @Override public void checkPermission(Permission perm) { check(perm, permissionManager::checkPermission); } @Override public void checkPropertyAccess(String key) { if (!shouldCheck()) { return; } String canonicalKey = key.intern().toLowerCase(); boolean allowedProperty = true; for (String property : forbiddenProperties) { if (canonicalKey.contains(property.toLowerCase())) { allowedProperty = false; break; } } if (!allowedProperty) { throw getException(key); } } @Override public void checkExec(String cmd) { if (!shouldCheck()) { return; } throw getException(cmd); } @Override public void checkExit(int status) { if (!checkForSecureAccess()) { throw getException("exit"); } } @Override public void checkDelete(String file) { if (!shouldCheck()) { return; } } @Override public void checkAccess(ThreadGroup g) { if (!shouldCheck()) { return; } } @Override public void checkAccess(Thread t) { if (!shouldCheck()) { return; } } @Override public void checkRead(String file) { if (file.endsWith("/org/intellimate/izou/security/SecurityModule.class")) return; if (!shouldCheck()) { return; } if (!getAddOnModelForClassLoader().isPresent()) { return; } permissionManager.getFilePermissionModule().fileReadCheck(file); } @Override public void checkWrite(FileDescriptor fd) { check(fd.toString(), permissionManager.getFilePermissionModule()::fileWriteCheck); } @Override public void checkPackageAccess(String pkg) { // Denies addOns access to packages in the list "forbiddenPackagesForAddOns" if (forbiddenPackagesForAddOns.contains(pkg) && (!checkForSecureAccess() && checkForAddOnAccess())) { throw getException("package: " + pkg); } } }