Return-Path: Received: from MIT.EDU by po10.mit.edu (8.9.2/4.7) id TAA23199; Thu, 1 Feb 2001 19:19:55 -0500 (EST) From: Received: from hermes.java.sun.com by MIT.EDU with SMTP id AA05365; Thu, 1 Feb 01 19:18:39 EST Date: Thu, 1 Feb 01 19:18:39 EST Message-Id: <10102020018.AA05365@MIT.EDU> To: alexp@MIT.EDU Subject: JDC Tech Tips January 30, 2001 Reply-To: JDCTechTips@sun.com Errors-To: root@hermes.java.sun.com Precedence: junk Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii X-Mailer: Beyond Email 2.2 J D C T E C H T I P S TIPS, TECHNIQUES, AND SAMPLE CODE WELCOME to the Java Developer Connection(sm) (JDC) Tech Tips, January 30, 2001. This issue of the JDC Tech Tips covers techniques for controlling access to Java(tm) packages. The topics covered are: * Controlling Package Access With Security Permissions * Controlling Package Access With Sealed JAR Files This tip was developed using Java(tm) 2 SDK, Standard Edition, v 1.3. This issue of the JDC Tech Tips is written by Stuart Halloway, a Java specialist at DevelopMentor (http://www.develop.com/java). You can view this issue of the Tech Tips on the Web at http://java.sun.com/jdc/TechTips/2001/tt0130.html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CONTROLLING PACKAGE ACCESS WITH SECURITY PERMISSIONS In the Java(tm) programming language, a package is an important abstraction that supports object-oriented design. A package is a group of classes that work together on some common task. For example, the java.io package handles input/output, and the java.net package handles network communications. Classes within the same package have a special relationship that is enforced by the language and the Java virtual machine*. If you declare a class, method, or field without an explicit access modifier, then it has default access, and is accessible to any class in the same package: package com.develop.widgets; //Both Helper and widgetCount are visible only within the //com.develop.widgets package. class Helper { static int widgetCount; } This level of access is invaluable for implementation details that need to be shared across multiple classes, and because of that, cannot be marked "private." However, default access only provides encapsulation if you control all the code that is loaded into your package. Otherwise, developers can accidentally (or maliciously) add their own classes to your package, and gain access to all the default-access classes, methods, and fields inside of the package. The Java platform provides several mechanisms to control package access. Two of these are built-in permissions relating to package access, and JAR sealing, which is supported by the java.net.URLClassLoader class. The Java security architecture defines two permission prefixes that control the use of packages: the RuntimePermissions that begin with "accessClassInPackage" and "defineClassInPackage." The accessClassInPackage permissions control whether a class can be loaded by a class loader, possibly by delegation to some other loader. These permissions are checked in the loadClass method of participating class loaders. The defineClassInPackage permissions allow a class to be defined by a specific class loader, and would probably be checked in the findClass method of a class loader. Depending on the class loaders in use in your application, you might need to specify both permissions. For example, if you want to be able to access the classes in the java.net and java.io packages from your application, you might specify a policy file entry like this: //correct, but unnecessary for reasons you will see momentarily grant { permission java.lang.RuntimePermission "accessClassInPackage.java.io"; permission java.lang.RuntimePermission "accessClassInPackage.java.net"; permission java.lang.RuntimePermission "defineClassInPackage.java.io"; permission java.lang.RuntimePermission "defineClassInPackage.java.net"; }; In each case, the suffix of the permission name is the name of the package. However, something is obviously missing from this story. Most Java policy files do not have any entries like these, yet Java applications are able to access these and other packages. The reason for this is that packages are not secured by default. To secure them, you must edit the java.security file in your ${JAVA_HOME}/jre/lib/security folder: #excerpted from java.security. Each property takes a comma delimited #list of package prefixes. Security checks apply only to packages #that begin with an exact string match of one of these prefixes. package.access=sun. #package.definition= As you can see, the only packages that are access protected are those prefixed with "sun." To see the protection in action, use the following test class, which simply verifies that it can load a class from a URL: //place in file TestAccess.java import java.net.*; import java.security.*; public class TestAccess { public static void testCL(ClassLoader ldr, String name) { try { Class cl = ldr.loadClass(name); System.out.println(ldr + " loaded " + cl); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { try { if (args.length != 2) { System.out.println("usage TestAccess URL class"); System.exit(-1); } URL[] urls = new URL[] {new URL(args[0])}; URLClassLoader ucl = URLClassLoader.newInstance(urls); testCL(ucl, args[1]); } catch (Throwable t) { t.printStackTrace(); } } } //place in file Test.policy grant { permission java.io.FilePermission "<>", "read"; permission java.lang.RuntimePermission "createClassLoader"; }; Try running this class with the following command line (that is, all on one line): java -Djava.security.manager -Djava.security.policy=Test.policy TestAccess file:someRandomURL/ sun.secret.Class The java.security.manager flag activates the default security manager. The java.security.policy property is set to Test.policy; the permissions in Test.policy allow your code to create a class loader and read from the local file system. If you run the class, you should see the following exception: java.security.AccessControlException: access denied (java.lang.RuntimePermission accessClassInPackage.sun.secret) It does not matter what URL you use, because the security check against the package name occurs before the class loader tries to access the URL. To give your code permission to access the sun.secret package, add the following entry to your Test.policy file: permission java.lang.RuntimePermission "accessClassInPackage.sun.secret"; If you retry the class with the same command line as before, you should see the more mundane error: java.lang.ClassNotFoundException: sun.secret.Class Of course, had there really been a sun.secret.Class, you would now have permission to access it. You can use the same mechanism to protect you own package names by adding the package prefixes to either the package.access or package.definition properties in the java.security file. You can then limit access to trusted code by adding entries to the policy file. There are a few caveats to this technique. First, you must make sure to use class loaders that participate in this part of the security architecture. The common class loaders' implementations of package security are summarized below: ---------------------------------------------------------- | Class Loader | Access Checks | Definition Checks | | ---------------------------------------------------------| | bootstrap | No | No | | extensions | No | No | | system | Yes | No | | URLClassLoader | Maybe* | No | ---------------------------------------------------------- As you can see, none of the class loaders provided with the JDK check for permission to define a class. If you want to add this capability, you need to use a custom class loader that overrides findClass. Also, beware the "Maybe" entry in the table for URLClassLoader. If you construct a URLClassLoader directly, it does not check if the package access is legal. But, if you use the static method URLClassLoader.newInstance to create a URLClassLoader, you receive a subclass of URLClassLoader that does check package access. The other caveat has to do with editing the java.security file. The package name passed to the security manager does not have a trailing "." and the string matching is completely literal. As a result, you must be careful in deciding whether to use a trailing period in the policy file. For example, the default entry: package.access=sun. protects "sun.misc" and "sun.tools" but it would not protect the sun package because there is no trailing period to match. But if the entry were edited to read: package.access=sun it would protect the sun package, but also other packages with names such as "sundance", "sung", and "sundry". You can read more about RuntimePermissions at http://java.sun.com/products/jdk/1.2/docs/guide/security/permissions.html#RuntimePermission - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CONTROLLING PACKAGE ACCESS WITH SEALED JAR FILES The security mechanisms discussed in the tip "Controlling Package Access With Security Permissions" approach the problem at a fairly low level. Most developers do not need this level of control, and simply want to guarantee that all classes in a package come from the same code source. This common case is supported by JAR "sealing." You can seal a JAR file by specifying a true value for the sealed attribute, that is: Sealed: true Note that the default value of the sealed attribute is false. After a class loader loads a class from a sealed JAR file, classes in the same package can only be loaded from that JAR file. You can override sealing on a per-package basis with additional attributes listed after the package name. For example, you can have a main section as follows (terminated by blank line): Sealed: false And follow the main section with sub-sections that have per-entry attributes: Name: com/develop/impl/ Sealed: true To see sealing in action, move the TestAccess class that was introduced in the tip "Controlling Package Access With Security Permissions" to a package named sealme: //place in file sealme/TestAccess.java package sealme; //repeat TestAccess.java contents from above Then, create a manifest that seals all packages. The file should have the following lines and end with a blank line: Manifest-Version: 1.0 Sealed: true Create a sealed jar file with the jar tool: jar cvmf manifest sealme.jar sealed/TestAccess.class Now, create another class in a separate location from TestAccess. Place it, say, in dynclass/sealme: package sealme; public class LoadMe { } Try running sealme.TestAccess from the jar file, using it to dynamically load sealme.LoadMe: java -cp sealme.jar sealme.TestAccess file:dynclass/ sealme.LoadMe Because the jar file is sealed, you will not be able to load sealMe.LoadMe from an alternate location. You should see an exception like this: java.lang.SecurityException: sealing violation at java.net.URLClassLoader.defineClass(URLClassLoader.java:234) (etc.) Notice that package sealing works regardless of whether you have installed a security manager. This is in marked contrast to the RuntimePermissions discussed in the tip "Controlling Package Access With Security Permissions." Where the permission-based solution is flexible but difficult to configure, the package sealing approach is simple, blunt, and easy to use. Unless you have a compelling reason to do otherwise, you should consider sealed JAR files to be the default mechanism for delivering JAVA code. It is interesting to note that the "java.-" packages are not protected by either of the schemes discussed above. The documentation is confusing on this point. For example, the documentation for the defineClassInPackage permission states that "[granting] this is dangerous because malicious code with this permission may define rogue classes in trusted packages like java.security or java.lang, for example." This is doubly wrong. First, the defineClassInPackage permission is not checked by any class loader supplied with the JDK. Second, as of JDK 1.3, the "java" packages are protected by a line that is hard coded into the ClassLoader class: //from java.lang.ClassLoader.defineClass if ((name != null) && name.startsWith("java.")) { throw new SecurityException("Prohibited package name: " + name.substring(0, name.lastIndexOf('.'))); } Obviously Sun takes the protection of the core API packages very seriously! You should take similar care when deploying own Java packages. JAR sealing is part of the Java extensions mechanism. For more information, see http://java.sun.com/products/jdk/1.2/docs/guide/extensions/index.html . . . . . . . . . . . . . . . . . . . . . . . - NOTE Sun respects your online time and privacy. The Java Developer Connection mailing lists are used for internal Sun Microsystems purposes only. You have received this email because you elected to subscribe. To unsubscribe, go to the Subscriptions page (http://developer.java.sun.com/subscription/), uncheck the appropriate checkbox, and click the Update button. - SUBSCRIBE To subscribe to a JDC newsletter mailing list, go to the Subscriptions page (http://developer.java.sun.com/subscription/), choose the newsletters you want to subscribe to, and click Update. - FEEDBACK Comments? Send your feedback on the JDC Tech Tips to: jdc-webmaster@sun.com - ARCHIVES You'll find the JDC Tech Tips archives at: http://java.sun.com/jdc/TechTips/index.html - COPYRIGHT Copyright 2001 Sun Microsystems, Inc. All rights reserved. 901 San Antonio Road, Palo Alto, California 94303 USA. This document is protected by copyright. For more information, see: http://java.sun.com/jdc/copyright.html JDC Tech Tips January 30, 2001