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}