diff --git a/testsrc/com/example/securitytest/SomeFactory.java b/testsrc/com/example/securitytest/SomeFactory.java new file mode 100644 index 0000000000..2cbb01fe1b --- /dev/null +++ b/testsrc/com/example/securitytest/SomeFactory.java @@ -0,0 +1,27 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package com.example.securitytest; + +/** + * Class for SecurityControllerTest. + * + * @author Roland Praml, FOCONIS AG + */ +public class SomeFactory { + + public static int TEST = 42; + + public SomeInterface create() { + try { + return (SomeInterface) + Class.forName("com.example.securitytest.impl.SomeClass").newInstance(); + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { + throw new RuntimeException("Could not create impl", e); + } + } +} diff --git a/testsrc/com/example/securitytest/SomeInterface.java b/testsrc/com/example/securitytest/SomeInterface.java new file mode 100644 index 0000000000..f01a7ae272 --- /dev/null +++ b/testsrc/com/example/securitytest/SomeInterface.java @@ -0,0 +1,18 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package com.example.securitytest; + +/** + * Class for SecurityControllerTest. + * + * @author Roland Praml, FOCONIS AG + */ +public interface SomeInterface { + + String foo(); +} diff --git a/testsrc/com/example/securitytest/impl/SomeClass.java b/testsrc/com/example/securitytest/impl/SomeClass.java new file mode 100644 index 0000000000..50c48a4db9 --- /dev/null +++ b/testsrc/com/example/securitytest/impl/SomeClass.java @@ -0,0 +1,33 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package com.example.securitytest.impl; + +import com.example.securitytest.SomeInterface; +import java.util.ArrayList; + +/** + * Provides an implementation for SomeInterface. Defines two methods: foo overridden + * (defined by interface) and bar defined at this class. + * + *

If this class is excluded by the shutter, the method bar should not be accessible + * in scripts. + * + * @author Roland Praml, FOCONIS AG + */ +public class SomeClass extends ArrayList implements SomeInterface { + private static final long serialVersionUID = 1L; + + @Override + public String foo() { + return "FOO"; + } + + public String bar() { + return "BAR"; + } +} diff --git a/testsrc/org/mozilla/javascript/tests/SecurityControllerTest.java b/testsrc/org/mozilla/javascript/tests/SecurityControllerTest.java new file mode 100644 index 0000000000..df5498f940 --- /dev/null +++ b/testsrc/org/mozilla/javascript/tests/SecurityControllerTest.java @@ -0,0 +1,123 @@ +package org.mozilla.javascript.tests; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.net.MalformedURLException; +import java.net.URL; +import java.security.CodeSource; +import java.security.Permission; +import java.security.Permissions; +import java.security.Policy; +import java.security.ProtectionDomain; +import java.security.URIParameter; +import java.util.Enumeration; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mozilla.javascript.ClassShutter; +import org.mozilla.javascript.EcmaError; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.SecurityController; +import org.mozilla.javascript.tools.shell.Global; +import org.mozilla.javascript.tools.shell.JavaPolicySecurity; + +/** Perform some tests when we have a securityController in place. */ +public class SecurityControllerTest { + + private static ProtectionDomain UNTRUSTED_JAVASCRIPT; + private static ProtectionDomain ALLOW_IMPL_ACCESS; + private static ProtectionDomain RESTRICT_IMPL_ACCESS; + protected final Global global = new Global(); + + /** Sets up the security manager and loads the "grant-all-java.policy". */ + static void setupSecurityManager() {} + /** Setup the security */ + @BeforeClass + public static void setup() throws Exception { + URL url = SecurityControllerTest.class.getResource("grant-all-java.policy"); + if (url != null) { + System.out.println("Initializing security manager with grant-all-java.policy"); + System.setProperty("java.security.policy", url.toString()); + Policy.getPolicy().refresh(); + System.setSecurityManager(new SecurityManager()); + } + SecurityController.initGlobal(new JavaPolicySecurity()); + + url = SecurityControllerTest.class.getResource("javascript.policy"); + Policy policy = Policy.getInstance("JavaPolicy", new URIParameter(url.toURI())); + RESTRICT_IMPL_ACCESS = createProtectionDomain(policy, "RESTRICT_IMPL_ACCESS"); + ALLOW_IMPL_ACCESS = createProtectionDomain(policy, "ALLOW_IMPL_ACCESS"); + } + + /** Creates a new protectionDomain with the given Code-Source Suffix. */ + private static ProtectionDomain createProtectionDomain(Policy policy, String csSuffix) + throws MalformedURLException { + URL url = new URL(SecurityController.class.getResource("/"), csSuffix); + CodeSource cs = new CodeSource(url, (java.security.cert.Certificate[]) null); + Permissions perms = new Permissions(); + Enumeration elems = policy.getPermissions(cs).elements(); + while (elems.hasMoreElements()) { + perms.add(elems.nextElement()); + } + perms.setReadOnly(); + return new ProtectionDomain(cs, perms, null, null); + } + + @Test + public void testBarAccess() { + // f.create produces "SomeClass extends ArrayList implements + // SomeInterface" + // we may access array methods, like 'size' defined by ArrayList, + // but not methods like 'bar' defined by SomeClass, because it is in a restricted package + String script = + "f = new com.example.securitytest.SomeFactory();\n" + + "var i = f.create();\n" + + "i.size();\n" + + "i.bar();"; + + // try in allowed scope + runScript(script, ALLOW_IMPL_ACCESS); + + try { + // in restricted scope, we expect an EcmaError + runScript(script, RESTRICT_IMPL_ACCESS); + fail("EcmaError expected"); + } catch (EcmaError ee) { + assertEquals("TypeError: Cannot find function bar in object []. (#4)", ee.getMessage()); + } + + // try in allowed scope again + runScript(script, ALLOW_IMPL_ACCESS); + } + + /** + * This classShutter checks the "rhino.visible.{pkg}" runtime property, which can be defined in + * a policy file. Note: Every other code in your stack-chain will need this permission also. + */ + private static class PolicyClassShutter implements ClassShutter { + + @Override + public boolean visibleToScripts(String fullClassName) { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + int idx = fullClassName.lastIndexOf('.'); + if (idx != -1) { + String pkg = fullClassName.substring(0, idx); + sm.checkPermission(new RuntimePermission("rhino.visible." + pkg)); + } + } + return true; + } + } + + /** Compiles and runs the script with the given protection domain. */ + private void runScript(String scriptSourceText, ProtectionDomain pd) { + Utils.runWithAllOptimizationLevels( + context -> { + context.setClassShutter(new PolicyClassShutter()); + Scriptable scope = context.initStandardObjects(global); + + return context.evaluateString(scope, scriptSourceText, "", 1, pd); + }); + } +} diff --git a/testsrc/org/mozilla/javascript/tests/grant-all-java.policy b/testsrc/org/mozilla/javascript/tests/grant-all-java.policy new file mode 100644 index 0000000000..c755fd7869 --- /dev/null +++ b/testsrc/org/mozilla/javascript/tests/grant-all-java.policy @@ -0,0 +1,5 @@ +// Grant everyone the following permission: (required for SecurityControllerTest) +grant { + // permission all; + permission java.security.AllPermission "", ""; +}; diff --git a/testsrc/org/mozilla/javascript/tests/javascript.policy b/testsrc/org/mozilla/javascript/tests/javascript.policy new file mode 100644 index 0000000000..4eeb60bd25 --- /dev/null +++ b/testsrc/org/mozilla/javascript/tests/javascript.policy @@ -0,0 +1,20 @@ +// Sample script, how to define different security codebases for javaScript. + +grant codebase "file:${user.dir}/bin/main/ALLOW_IMPL_ACCESS" { + permission java.lang.RuntimePermission "rhino.visible.com"; + permission java.lang.RuntimePermission "rhino.visible.com.example"; + permission java.lang.RuntimePermission "rhino.visible.com.example.securitytest"; + permission java.lang.RuntimePermission "rhino.visible.com.example.securitytest.*"; +}; + +grant codebase "file:${user.dir}/bin/main/RESTRICT_IMPL_ACCESS" { + permission java.lang.RuntimePermission "rhino.visible.com"; + permission java.lang.RuntimePermission "rhino.visible.com.example"; + permission java.lang.RuntimePermission "rhino.visible.com.example.securitytest"; +}; + +grant { + // grant every script access to java.lang and java.util (but not to java.util.*) + permission java.lang.RuntimePermission "rhino.visible.java"; + permission java.lang.RuntimePermission "rhino.visible.java.*"; +};