text: .
  .
  Working.
  Once the class file verifier has successfully completed the pass two checks, it turns its attention to the bytecodes. During this pass, Java virtual machine to execute.
  The bytecode verifier doesn't attempt to detect all safe programs. If it tried to do that, it would run up against the Halting Problem. The Halting Problem, a well-known theorem in computer science, states that you can't write a program that can determine whether any program fed to it as input will halt when it is executed. Whether or not a program will halt is called an "undecidable" property of the program, because you can't write a program that can tell you 100% of the time whether or not any given program has the property. The undecideability of the Halting Problem extends to many properties of computer programs, including whether or not a set of Java bytecodes would be safe for a Java virtual machine to execute.
  The way the bytecode verifier gets around the Halting Problem is by not attempting to pass all safe programs. Although you can't write a program that can determine whether or not any given program will halt, you can write a program that recognizes some programs that will halt. For example, if the first instruction of a program is halt, that program will halt. If a program has no loops in it, it will halt, and so on. Similarly, although you can't write a verifier that will pass all bytecode streams that are safe for the virtual machine to execute, you can write a verifier that will pass some of them. And that's just what Java's bytecode verifier does. The verifier checks to make sure a certain set of rules are followed by each set of bytecodes its fed. If a set of bytecodes obeys all the rules, the verifier knows the bytecodes are safe for the virtual machine to execute. If not, the bytecodes may or may not be safe for the virtual machine to execute. Thus, the verifier gets around the Halting Problem by recognizing some, but not all, safe bytecode streams. Given the nature of the constraints checked by the bytecode verifier, any program that can be written in the Java programming language can be compiled to bytecodes that will pass the verifier. Some programs that could not possibly be expressed in the Java programming language will pass the verifier. And some programs (also not expressible in Java source code) that would otherwise be safe for the virtual machine to execute, will not pass the verifier.
  Passes one, two, and three of the class file verifier make.
  Pass four of the class file verifier takes place when the symbolic references contained in a class file are
  resolved in the process of dynamic linking. During pass four, the Java virtual machine follows the
  references from the class file being verified to the referenced class files, to make sure the references are
  correct. Because pass four has to look at other classes external to the class file being checked, pass four may
  require that new classes be loaded. Most Java virtual machine implementations will likely delay loading
  classes until they are actually
  NoClassDefFoundError error until
  (and unless) the referenced class is used for the first time by the running program. Thus, if a Java virtual
  machine performs early linking, pass four could happen shortly after pass three. But in Java virtual
  machines that resolve each symbolic reference the first time it is used, pass four will happen much later than
  pass three, as bytecodes are executed.
  Pass four of class file verification is really just part of the process of dynamic linking. When a class file is loaded, it contains symbolic references to other classes and their fields and methods..
  Dynamic linking is the process of resolving symbolic references into direct references. As the Java virtual machine executes bytecodes and encounters an opcode that, for the first time, uses a symbolic reference to another class, the virtual machine must resolve the symbolic reference. The virtual machine performs two basic tasks during resolution:
  When the Java virtual machine resolves a symbolic reference, pass four again the
  Volcano class. If a method of class
  Volcano invokes a method in a class named
  Lava, the name and
  descriptor of the method in
  Lava are included as part of the binary data in the class file
  for
  Volcano. When
  Volcano's method first invokes
  Lava's method during the course of execution, the Java virtual machine, pass four verification fails, and the Java virtual machine throws a
  NoSuchMethodError.
  The reason pass four of the class file verifier must look at classes that refer to one another to make sure they are compatible is because Java programs are dynamically linked. Java compilers will often recompile classes that depend on a class you have changed, and in so doing, detect any incompatibility pass four of the class file verifier must check for compatibility at run-time.
  As an example of incompatible changes, imagine you compiled class
  Volcano
  (from the previous pass four class file verification,
  throw a
  NoSuchMethodError.
  Once the Java virtual machine has loaded a class and performed passes one through three of class file verification, the bytecodes are ready to be executed. Besides the verification of symbolic references (pass four of class file verification), the Java virtual machine has several other built-in security mechanisms operating as bytecodes are executed. These mechanisms, most of which are elements of Java's type safety, are listed in Chapter 1 as features of the Java programming language that make Java programs robust. They are, not surprisingly, also features of the Java virtual machine:
  null
  By granting a Java program only type-safe, structured ways to access memory, the Java virtual machine makes Java programs more robust, but it also makes their execution more secure. A program that corrupts memory, crashes, and possibly causes other programs to crash represents one kind of security breach. If you are running a mission critical server process, for example, it is critical that the process doesn't crash. This level of robustness is also important in embedded systems, such as a cell phone, which people don't usually expect to have to reboot. firm barrier against the malicious manipulation of memory.
  There is, however, a way to penetrate the security barriers erected by the mechanisms that support type safety in. Untrusted first three prongs of Java's security model -- the class loader architecture, class file verifier, and safety features built into Java -- all work together to achieve a common goal: to protect the internal integrity of a Java virtual machine instance and the application it is running from malicious or buggy code it may load. By contrast, the fourth prong of the security model, the security manager, is geared towards protecting assets external to the virtual machine from malicious or buggy code running within the virtual machine. The security manager is a single object that serves as the central point for access control -- the controlling of access to external assets -- within a running Java virtual machine.
  The security manager defines the outer boundaries of the sandbox. Because it is customizable, the
  security manager allows a custom security policy to be established for an application. The Java API
  enforces the custom security policy by asking the security manager for permission before it takes any action
  that is potentially unsafe. To ask the security manager for permission, the methods of the Java API invoke
  "check methods" on the security manager object. These methods are called check methods because their
  names all begin with the substring "check." For example, the security manager's
  checkRead() method determines whether or not a thread is allowed to read to a
  specified file. The
  checkWrite() method determines whether or not a thread is
  allowed to write to a specified file. The implementation of these methods is what defines the custom
  security policy of the application.
  Because the Java API always checks with the security manager before it performs a potentially unsafe action, the Java API will not perform any action forbidden under the security policy established by the security manager. If the security manager forbids an action, the Java API won't perform that action.
  When a Java application starts, it has no security manager, but the application can install one at its
  option by passing a reference to an instance of
  java.lang.SecurityManager or
  one of its subclasses to
  setSecurityManager(), a static method of class
  java.lang.System. If an application does not install a security manager, there are
  no restrictions in 1.0 or 1.1 that
  security manager will be in charge for the entire remainder of the lifetime of that application. It can't be
  replaced, extended, or changed. From that point on, the Java API will only fulfill those requests that are
  sanctioned by the security manager. In 1.2, however, the currently installed security manager can be
  replaced by code that has permission to replace it by invoking
  System.setSecurityManager() with a reference to a different security
  manager object..
  As mentioned earlier in this chapter, the security manager is responsible for two things: for specifying a security policy and for enforcing that policy. The security policy, which states what kind of code will be allowed to take what kind of actions, is defined by the code of the security manager's check methods. The policy is enforced by the behavior of the check methods when they are invoked.
  Prior to 1.2,
  java.lang.SecurityManager was an abstract class. To
  establish a custom security policy in 1.0 or 1.1, you had to write your own security manager by subclassing
  SecurityManager and implementing its check methods. Your application would
  instantiate and install the security manager, which from that point forward for the remainder of the life of
  the application would enforce the security policy you defined in the code of its check methods.
  Although the customizability of the security manager was one of the greatest strengths of Java's security
  model, it was also a potential point of weakness. Writing a security manager is a complicated and error
  prone task. Any mistakes made when implementing the check methods of a security manager could
  potentially translate into security holes at runtime. To help make it easier and less error prone for
  developers and end-users to establish fine-grained security policies based on signed code, the
  java.lang.SecurityManager class in the version 1.2 is a concrete class that
  provides a default implementation of the security manager. (In the remainder of this book, this default
  implementation of the security manager provided with version 1.2 will be called the "concrete
  SecurityManager.") Your application can instantiate and install this security
  manager explicitly, or allow it to be installed automatically. In Sun's Java 2 SDK version 1.2, for example,
  you can specify that the concrete
  SecurityManager be installed by using the
  -Djava.security.manager option on the command line.
  The concrete
  SecurityManager class allows you to define your custom policy
  not in Java code, but in an ASCII file called a policy file. In the policy file, you grant
  permissions to code sources. Permissions are defined in terms of classes that are
  subclasses of
  java.security.Permission. For example,
  java.io.FilePermission represents permission to read, write, execute, or delete
  a file. Code sources are composed of a codebase URL from which the code was loaded and a set of signers
  that vouched for the code. When the security manager is created, it parses the policy file and creates
  CodeSource and
  Permission objects. These objects are
  encapsulated in a single
  Policy object that expresses the policy at runtime. Only one
  Policy object can be installed at any one time.
  Class loaders place types into protection domains, which encapsulate all the permissions granted to the code source represented by the loaded type. Each type loaded into a 1.2 virtual machine belongs to one and only one protection domain. The protection domain is remembered and is used when deciding whether or not the code will be allowed to take potentially unsafe actions.
  When the check methods of the concrete
  SecurityManager are invoked, most
  of them pass the request on to a class called the
  AccessController. The
  AccessController, using the information contained in the protection domain
  objects of the classes whose methods are on the call stack, performs stack inspection to determine whether
  the action should be allowed.
  The security manager has undergone quite a bit of change in 1.2. In versions 1.0 and 1.1, each check
  method indicates what is being checked in its method name. To check whether or not it is OK to read a
  certain file, the Java API invokes the
  checkRead() method on the security manager
  and passes and the path name of the file to read as a parameter. For example, before attempting to read a
  file named
  /tmp/finances.dat, the security manager invokes
  checkRead("/tmp/finances.dat") on the security manager.
  The security manager declares 28 of these check methods, which in the remainder of this chapter will be
  referred to as "legacy check methods." Although new methods were added to the security manager in 1.2
  that would otherwise render these legacy check methods obsolete, to maintain backwards compatibility the
  Java API continues to call the legacy check methods just as it did in prior releases.
  The 28 legacy check methods are listed here along with the potentially unsafe action that trigger's their invocation by the code of the Java API:
  checkConnect(String host, int port)- open a socket connection to the specified host and port number
  checkConnect(String host, int port, Object context)- open a socket connection to the specified host and port number under the passed security context
  checkAccept(String host, int port)- accept a socket connection from the specified host and port number
  checkCreateClassLoader()- create a new class loader
  checkAccess(Thread t)- modify a thread (change its priority, stop it, etc...)
  checkAccess(ThreadGroup t)- modify a thread group (add a new thread, set deamonness, etc...)
  checkExit()- cause the application to exit
  checkLink()- load a dynamic library that contains native methods
  checkRead(FileDescriptor fd)- read from the specified file
  checkRead(String file)- read from the specified file
  checkRead(String file, Object context)- read from the specified file under the passed security context
  checkWrite(FileDescriptor fd)- write to the specified file
  checkWrite(String file)- write to the specified file
  checkDelete(String file)- delete the specified file
  checkListen(int port)- wait for a connection on the specified local port number
  checkMulticast(InedAddress maddr)- join, leave, send, or receive IP multicast
  checkMulticast(InedAddress maddr, byte ttl)- join, leave, send, or receive IP multicast
  checkPropertiesAccess()- access or modify system properties in general
  checkPropertiesAccess(String key)- access or modify the specified system property
  checkTopLevelWindow(Object Window)- bring up the specified window without any warning
  checkPrintJobAccess()- initiate a print job request
  checkSystemClipboardAccess()- access the system clipboard
  checkAWTEventQueueAccess()- access the AWT event queue
  checkPackageAccess(String pkg)- access types from the specified package (used by class loaders)
  checkPackageDefinition(String pkg)- add a new class to the specified package (used by class loaders)
  checkSetFactory()- set the socket factory used by
  ServerSocketor
  Socketor set the URL stream handler used by
  URL
  checkMemberAccess()- access class information via the reflection API
  In 1.2, a set of permission classes was defined whose instances represent actions code is allowed to
  take. A new pair of check methods were added in 1.2 to class
  java.lang.SecurityManager, both
  named
  checkPermission():
  checkPermission(Permission perm)- take an action that requires the specified permission
  checkPermission(Permission perm, Object context)- take an action that requires the specified permission under the passed security context
  The
  checkPermission() methods accept a reference to a
  Permission object, which indicates the action that is being requested. Thus, this
  method provides an alternative way to ask the security manager if it is OK to perform a potentially unsafe
  action. For example, to determine whether it is OK to read file
  /tmp/finances.dat, the Java API in 1.2 could take either of two approaches. The
  Java API could take the old fashioned approach, and invoke the legacy method
  checkRead() passing the
  String
  "/tmp/finances.dat" as a parameter. Or, the Java API could take the fresh new
  approach. It could create a
  java.io.FilePermission object, passing
  Strings
  "/tmp/finances.dat" and
  "read" to the
  FilePermission constructor. The Java API
  could then pass this
  Permission object to the security manager's
  checkPermission() method.
  Both the old fashioned approach of invoking a legacy check method and the fresh new approach of
  creating a permission object and invoking
  checkPermission() should yield the
  same result. To maintain backwards compatibility with security managers that were written for 1.0 or 1.1,
  however, the 1.2 Java API continues to take the old fashioned approach. The 1.2 Java API continues to call
  the 28 legacy check methods. Nevertheless, in the concrete
  SecurityManager class,
  the legacy methods are for the most part implemented in terms of the new
  checkPermission() method. So by invoking the legacy method on the concrete
  SecurityManager, the Java API is indirectly invoking the
  checkPermission() method anyway. For example, the
  checkRead() method implementation in the concrete
  SecurityManager simply instantiates a new
  FilePermission object, passing the pathname
  String passed
  to it to the
  FilePermission's constructor, along with the
  String
  "read". The
  checkRead()
  method then invokes
  checkPermission(), passing a reference to the
  FilePermission object.
  The Java API may at times also invoke
  checkPermission() directly. For new
  concepts of potentially unsafe actions introduced in 1.2 and later versions, no legacy check methods exist.
  Thus, in some situations, the Java API may create a new
  Permission object for
  which no relevant check methods exists, and pass that
  Permission object directly to
  the security manager's
  checkPermission() method.
  In the concrete
  SecurityManager class, the
  checkPermission() method also delegates the job of deciding whether or not to
  allow the action to another method. The concrete
  SecurityManager's
  checkPermission() method simply invokes the static
  checkPermission() method of class
  java.security.AccessController, passing along the permission object. The
  AccessController class, therefore, is the actual entity responsible for enforcing
  the security policy when you use the concrete
  SecurityManager.
  All of these changes in 1.2 are backwards compatible with 1.1 and 1.0. If you created a security
  manager for 1.1, it should still work as expected in 1.2. You can still create a custom security manager in
  1.2 as well, which allows anyone with special security needs that aren't adequately addressed by the
  concrete
  SecurityManager implementation to create a different kind of security
  infrastructure. Most people's security needs, however, will be likely met by taking advantage of the
  flexibility and extensibility built into the concrete
  SecurityManager.
  A critical piece of Java's security model is the support for authentication introduced in Java 1.1 in the
  java.security package and its subpackages. The authentication capabilities
  expand your ability to establish multiple security policies by enabling you to implement a sandbox that
  varies depending upon who vouched for the code. Authentication allows you to verify that a set of class
  files was blessed as trustworthy by some party, and that the class files were not altered en route to your
  virtual machine. Thus, to the extent you trust the party who vouched for the code, you can ease the
  restrictions placed on the code by the sandbox. You can establish different security restrictions for code that
  is signed by different parties.
  To vouch for, or sign, a piece of code, you must first generate a public/private key pair. You should
  keep the private key private, but can make the public key public. At the very least, you must somehow get
  the public key to anyone who wants to establish a security policy based on your signature. (As will be
  illustrated later in this section, distributing public keys is not necessarily as easy as it may seem.) Once you
  have a public/private key pair, you must place the class files and any other files you want to sign into a JAR
  file. You then use a tool, such as
  jarsigner from the 1.2 SDK, to sign the entire JAR
  file. The signer tool will first perform a one-way hash calculation on the contents of the JAR file to generate
  a hash. The tool will then sign the hash with your private key, and add the signed hash to the JAR file. The
  signed hash represents your digital signature of the contents of the JAR file. When you distribute the JAR
  file that contains the signed hash, anyone with your public key can verify two things about the JAR file: the
  JAR file was indeed signed by you, and the contents of the JAR file were not in any way altered since you
  attached your signature.
  The first step in the digital signing process is the one-way hash calculation, which takes a big number as input and generates a small number, called the hash. In the case of a JAR file, the big-number input to the calculation is the stream of bytes that make up the contents of the JAR file. The one-way hash calculation is called "one-way" because given just the hash (the small number), it is impossible to calculate the input (the big number). In other words, the hash value doesn't contain enough information about the input to enable the input to be regenerated from the hash. The calculation goes just one way, from big to small, from input to hash.
  The hash, which is also called a message digest, serves as a kind of "fingerprint" for the input. Although different inputs can produce the same hash, the hash is considered unique enough in practice to represent the input from which it was generated. Much like a fingerprint can be used to identify the individual who made the fingerprint, a hash can be used to identify the input that caused the one-way hash algorithm to produce the hash. The hash is used during the authentication process to verify that the input is identical to the input that produced the original hash, in other words, that the input was not changed en route to its destination.
  Given that it is impossible to reconstruct the input given just the hash, a hash is only useful if the input is also available. Thus, you normally transmit both input and hash together. By themselves, the combination of an input and its hash is not secure, however, because even an extremely unimaginative cracker could simply replace both the input and the hash. To prevent this scenario, you encrypt the hash with your private key before sending it. The reason you encrypt the hash rather than simply encrypting the entire JAR file is that private key encryption is a time-consuming process. It is in general much faster to calculate a one-way hash from the JAR file contents and encrypt the hash with a private key than it is to encrypt the entire JAR file with the private key. A cracker will only be able to replace both an input and encrypted hash if the cracker has your private key, which you are supposed to keep secret. Thus, the combination of input and encrypted hash is more frustrating to a potential cracker than the mere combination of input and hash because, in theory, the cracker doesn't have your private key.
  Anything encrypted with your private key can be decrypted with your public key. Public/private key pairs have the characteristic that it is prohibitively difficult given just the public key to generate the private key. If you can keep your private key out the hands of crackers, therefore, their best option is to try and replace the input with a different input that yields the same hash value. If the cracker wishes to replace one class file in your JAR file with a different class file that performs some devious act, for example, the odds are extremely high that the revised JAR file (the one that contains the devious class file) will produce a different hash. But the cracker could add random data to the JAR file until the one-way hash calculation on the altered JAR file produces the same hash value as the original. If the cracker can produce such an alternative input -- one that both helps the cracker achieve his or her nefarious goals and generates the same hash as your original input -- the cracker would not need your private key. Because the cracker's input generates the same hash value as your original input, and you have already signed that hash value with your private key, the cracker can simply place your signed hash in a JAR file with his or her input. What's to prevent a cracker from taking this approach? Unfortunately for the cracker, such an approach would likely take too much time to be feasible.
  Because one-way hash algorithms generate a small number (the message digest or hash) from a big number (the input), different inputs can produce the same hash. One-way hash algorithms tend to spread out the inputs that produce the same hash sufficiently randomly that the likelihood of getting the same hash value depends primarily on the size of the hash. For example, if you use a hash value that is 8 bits wide, your one-way hash algorithm will have only 256 unique hash values from which to choose. If you have a JAR file that produces the hash value 100 and you start calculating the 8 bit hash with this algorithm on other JAR files, you shouldn't be surprised if every 256 times or so you get the hash value 100. The more bits contained in the hash, of course, the less often the algorithm will produce the same hash. In practice, 64- and 128-bit hash values are common, which are considered large enough to render the process of finding a different input that produces the same hash computationally infeasible. The main barrier preventing a cracker from replacing your benevolent input with a malicious input that serves the cracker's evil purposes and produces the same hash, therefore, is the time and resources the cracker would have to devote to searching for that malicious input.
  The last step in the digital signing process, after you have generated the hash value and encrypted it with your private key, is to add the encrypted hash value to the same JAR file that contains the files from which you generated the hash value originally. A signed JAR file, therefore, contains the input -- the class and data files you wanted to vouch for -- plus the hash value (generated from the input) encrypted with your private key. The encrypted hash represents your digital signature of the class and data files contained in the same JAR file. The process of signing a JAR file is shown graphically in Figure 3-3.
  To authenticate a JAR file that you have purportedly signed, the recipient must decrypt the signed hash with your public key. The result should be equal to the original hash that you calculated on the contents of the JAR file. To verify that the JAR file contents were not changed since you signed them, the recipient simply applies the one-way hash algorithm on the contents of the JAR file, just as you did during the signing process. (Remember you never encrypted contents of the JAR file, so anyone can see them. You only added a digital signature to the JAR file.) If the hash value generated by the algorithm matches the decrypted hash value, the recipient concludes that you did indeed vouch for this JAR file and that the contents of the JAR file did not change since you added your signature. The code contained in the JAR file can be placed inside a relaxed sandbox that represents the trust the recipient places in your signature. The process of verifying a digitally signed JAR file is shown in Figure 3-4.
  Although the authentication technology first introduced in Java version 1.1 is firmly founded in reliable mathematics, the math doesn't solve every problem. In fact, several questions are raised by Java's authentication technology. For example, the authentication technology says nothing about who you should trust, and to what extent you should trust them. To what extent do you trust some small company that you've never heard of? To what extent do you trust a big company whose name is a household word? To what extent do you trust a different department in your own company? What are the chances that any particular company (or department) has a rogue employee who managed to slip a time bomb into a JAR file the company signed? No cryptographic algorithm can answer these questions for you.
  Another security issue stems from the assumption inherent in the authentication technology that private keys will be kept under lock and, well, key. If private keys are not kept private, the entire authentication scheme is reduced to strenuous mathematical activity that is not only ineffective, but dangerous, because it can give a false sense of security. You are responsible for keeping your own private keys private. You can only hope that any entity on whose signature authority you grant code access to your system has kept their private keys private. For any party, establishing a key management scheme that prevents private keys from being leaked (remember those rogue employees?) can be a challenging task.
  Another question raised by the technology involves the distribution of public keys. Although it may seem surprising at first, the assumption inherent in the authentication technology that public keys will be made public creates some security issues of its own. For example, imagine you want to relax your sandbox for code vouched for by a guy named Evan. To do so, you need Evan's public key. But how exactly do you get his public key? If you know Evan personally, you can invite him over for coffee and ask him to bring his public key so he can give it to you in person. But what if you don't know Evan personally? You might think you could simply visit Evan's web site and grab his public key off a web page. Or alternatively, perhaps you could phone Evan and ask him to send you his public key in an e-mail. Evan should have no problem sending you, a stranger, his public key, because public keys after all are designed to be public. Evan doesn't need to worry about who gets his public key. He could hire a biplane to write his public key on the sky over Silicon Valley and still feel confident he was operating within the rules delineated by Java's authentication technology. So what's the problem? The problem is that even though Evan doesn't need to worry about your identity when he sends you his public key, you need to worry about his. Evan will be happy to send you his public key, but how do you know that the public key you receive is really the one that Evan sent?
  The difficulty of public key distribution is that no matter what the means of communication, the message -- the public key -- could potentially be tampered with in transit. When you visit Evan's web page, it is possible that the web page is intercepted and changed en route to your browser, perhaps by Dastardly Doug, a cracker of international repute. When you think you are copying Evan's public key off his web page, you could actually be copying Dastardly Doug's. Doug could also have intercepted Evan's e-mail and replaced Evan's beneficent public key with his own dastardly public key. Doug could even have donned one of his many clever disguises and piloted the biplane high above Silicon Valley, inscribing his public key among the clouds in place of Evan's. If Doug can successfully replace Evan's public key with his own, Doug can pretend to be Evan and take advantage of the trust you place in Evan's signature to break into your system.
  But wait a minute, isn't the difficulty of public key distribution just another authentication problem, the kind of problem the authentication technology itself is designed to address? In fact it is, and by turning authentication back on itself, Evan can make it far more difficult for Dastardly Doug to replace Evan's public key with his own.
  To address the difficulties of public key distribution, several certificate authorities have been established for the purpose of vouching for public keys. Evan, for example, could go to a certificate authority and present his credentials (birth certificate, drivers license, passport, and so on) and his public key. Once convinced that Evan is who he says he is, the certificate authority would sign Evan's public key with the certificate authority's private key. The resulting sequence of numbers is called a certificate. Instead of distributing his public key, then, Evan would distribute his certificate.
  You could grab Evan's certificate off of his web page, out of an e-mail, or via any other unsecured communications medium. When you get the certificate, you decrypt it with the certificate authority's public key and are rewarded with Evan's public key. The certificate scheme makes it much less likely that Doug will be able to swap his public key for Evan's because to do so, Doug would need the certificate authority's private key.
  Although certificates improve the public key distribution situation immensely, some issues still remain. First of all, how exactly to you get the certificate authority's public key? You need this public key to authenticate the public keys of anyone else. Well, if you know any employees of the certificate authority personally, you could invite them over for coffee and ask them to bring their public key to give to you in person. But what if you don't know any employees of the certificate authority personally? And then there is the nagging question: why should you trust the certificate authority? A certificate authority can pretend to be anyone. Isn't a certificate authority just as susceptible as the next company to the vagaries of rogue employees?
  Despite all these issues, the code signing capabilities introduced in Java 1.1 generally offer you enough security to enable you to relax your sandbox when you need to. Although the authentication technology doesn't eliminate all risk associated with relaxing the sandbox, it can help minimize the risks. Security is a tradeoff between cost and risk: the lower the security risk, the higher the cost of security. You must weigh the costs associated with any computer or network security strategy against the costs of the theft or destruction of the information or computing resources being protected. The nature of your computer or network security strategy should be shaped by the value of the assets you are trying to protect. Java's authentication technology is a useful tool that, in concert with Java's sandbox, can help you manage the costs and risks of running network-mobile code on your systems.
  For an example of code signing with the
  jarsigner tool of the Java 2 SDK 1.2,
  consider the following types,
  Doer,
  Friend, and
  Stranger. The first type,
  Doer, defines an interface that the
  other two types, classes
  Friend and
  Stranger implement:
  // On CD-ROM in file // security/ex2/com/artima/security/doer/Doer.java package com.artima.security.doer; public interface Doer { void doYourThing(); }
  Doer declares just one method,
  doYourThing(). Class
  Friend and class
  Stranger implement this method in the exact
  same way. In fact, besides their names, the two classes are identical:
  //Controller.doPrivileged( new PrivilegedAction() { public Object run() { next.doYourThing(); return null; } } ); } } } // On CD-ROM in file // security/ex2/com/artima/security/stranger/Stranger.java package com.artima.security.stranger; import com.artima.security.doer.Doer; import java.security.AccessController; import java.security.PrivilegedAction; public class Stranger implements Doer { private Doer next; private boolean direct; public Stranger(Doer next, boolean direct) { this.next = next; this.direct = direct; } public void doYourThing() { if (direct) { next.doYourThing(); } else { AccessController.doPrivileged( new PrivilegedAction() { public Object run() { next.doYourThing(); return null; } } ); } } }
  These types --
  Doer,
  Friend, and
  Stranger -- are designed to illustrate the stack inspection mechanism of the access
  controller. The motivation behind their design will be made clear later in this chapter, when several
  examples of stack inspection are given. At this point, however, the class files generated by compiling
  Friend and
  Stranger must be signed to prepare them for the
  upcoming stack inspection examples. The class files generated from
  Friend.java
  will be signed by a party referred to fondly as "
  friend." The class files generated
  from
  Stranger.java will be signed by a party referred to somewhat suspiciously as
  "
  stranger." The class file generated by
  Doer will not be signed.
  To prepare the class files for signing, they must first be placed into JAR files. Because the class files for
  Friend and
  Stranger need to be signed by two different
  parties, they will be collected into two different JAR files. The two class files generated by compiling
  Friend.java,
  Friend.class and
  Friend$1.class, will be placed into a JAR file called
  friend.jar. Similarly, the two class files generated by compiling
  Stranger.java,
  Stranger.class and
  Stranger$1.class, will be placed into a JAR file called
  stranger.jar. (Note that although all of the files in these examples are in the
  security/ex2 directory of the CD-ROM, to repeat any of the commands that
  generate files, you'll have to copy the entire
  security/ex2 directory hierarchy to a
  writable media, such as a hard disk. But you probably knew that already.)
  Friend.java's class files are dropped by the
  javac
  compiler in the
  security/ex2/com/artima/security/friend directory.
  Because class
  Friend is declared in the
  com.artima.security.friend package,
  Friend.java's
  class files must be placed in the JAR file in the
  com/artima/security/friend
  directory. The following command, executed in the
  security/ex2 directory, will
  place
  Friend.class and
  Friend$1.class into a newly
  created JAR file called
  friend.jar, which is placed in the current directory,
  security/ex2:
  jar cvf friend.jar com/artima/security/friend/*.class
  Once the previous command completes, the class files for
  Friend.java must be
  removed so they won't be found by the Java virtual machine when it runs the access control examples:
  rm com/artima/security/friend/Friend.class rm com/artima/security/friend/Friend$1.class
  Filling a JAR file with
  Stranger.java's class files, which are dropped by
  javac in the
  security/ex2/com/artima/security/stranger directory, requires a
  similar process. From the
  security/ex2 directory, the following command must be
  executed:
  jar cvf stranger.jar com/artima/security/stranger/*.class rm com/artima/security/stranger/Stranger.class rm com/artima/security/stranger/Stranger$1.class
  To sign a JAR file with the
  jarsigner tool from the Java 2 SDK 1.2, a
  public/private key pair for the signer must already exist in a keystore file, which is a file for
  storing named, password-protected keys. The
  keytool program of the Java 2 SDK
  1.2, can be used to generate a new key pair, associate the key pair with a name or alias, and
  protect it with a password. The alias, which is unique within each keystore file, is used to identify a
  particular key pair in a particular keystore file. The password for a key pair is required to access or change
  the information contained in the keystore file for that key pair.
  The access control examples expect a keystore file named
  ivjmkeys in the
  security/ex2 directory containing key pairs for the aliases
  "
  friend" and "
  stranger." The following command, executed
  from the
  security/ex2 directory, will generate the key pair for the alias,
  friend, with the password,
  friend4life. In the process, it
  will create the keystore file named
  ijvmkeys:
  keytool -genkey -alias friend -keypass friend4life -validity 10000 - keystore ijvmkeys
  The
  -validity 10000 command line argument of the previous
  keytool command indicates that the key pair should be valid for 10000 days, which at
  over 27 years, is likely enough time to outlive the product lifecycle of this edition of this book. When the
  command runs, it will prompt for a keystore password, which is a general password required for any kind of
  access or change of the keystore file. The keystore password given to
  ijvmkeys is
  "ijvm2ed".
  The key pair for
  stranger can be generated with a similar command:
  keytool -genkey -alias stranger -keypass stranger4life -validity 10000 - keystore ijvmkeys
  Now that the keystore file
  ijvmkeys contains key pairs for
  friend and
  stranger, and the JAR files
  friend.jar and
  stranger.jar contain the appropriate class
  files, the JAR files can finally be signed. The following
  jarsigner command,
  executed from the
  examples/ex2 directory, will sign the class files contained in
  friend.jar using
  friend's private key:
  jarsigner -keystore ijvmkeys -storepass ijvm2ed -keypass friend4life friend.jar friend
  A similar command will sign the class files contained in
  stranger.jar with
  stranger's private key:
  jarsigner -keystore ijvmkeys -storepass ijvm2ed -keypass stranger4life stranger.jar stranger
  Whew, that was a lot of work just to sign two JAR files. And keep in mind that in the real world, you'd have to make sure no one with bad intent got a hold of your private keys, and that you kept track of them. That means not losing the keystore file, remembering the passwords, and so on. In addition, you'll have to get your public keys to anyone who is going to use your signature to give your code access to their system.
  As mentioned previously, one of the greatest advantages of Java's sandbox security model is that the sandbox can be customized. The code signing and authentication technology introduced in Java version 1.1 enables your running application to differentiate code to which you attribute different degrees of trust. By customizing the sandbox, trusted code can be given more access to system resources than untrusted code. This prevents untrusted code from accessing the system, but allows trusted code to access the system and do useful work. The real power of Java's security architecture, however, lies in the ability to grant code with varying degrees of trust different levels of partial access to the system.
  Microsoft offers an authentication technology similar to Java's for ActiveX controls, but ActiveX controls don't run inside a sandbox. Thus with ActiveX, a chunk of mobile code is either completely trusted or completely untrusted. If untrusted, the ActiveX control is denied the opportunity to run. If trusted, the ActiveX control is allowed to run and given full access to the system. While this is a big improvement over no authentication at all, if some malicious or buggy code gets authenticated, the dangerous code has full access to the system. One of the strengths of Java's security architecture is that code can be given access only to the resources it needs. If some malicious or buggy code gets authenticated, it has less opportunity to do damage. For example, instead of being able to delete all files on a local hard disk, the malicious or buggy code might only be able to delete the files in a particular directory set aside just for it.
  One major goal of the 1.2 security infrastructure is to make it easier and less error prone to establish fine-grained access control policies based on signed code. To be able to assign different system access privileges to different units of code, Java's access control mechanism must be able to ascertain what privileges should be given to each individual piece of code. To facilitate this process, each piece of code (each class file) loaded into a 1.2 or beyond Java virtual machine is associated with a code source. The code source basically says where the code came from and who, if anyone, has vouched for the code by signing it. In the 1.2 security model, permissions (system access privileges) are assigned to code sources. Thus, if a piece of code requests access to a particular system resource, the Java virtual machine will grant the code access to that resource only if such access is a privilege associated with that code's code source.
  In the 1.2 security infrastructure, an access control policy for an entire Java application is represented
  by a single instance of a subclass of the abstract class
  java.security.Policy.
  Each application has just one
  Policy object in effect at any given time. Code that has
  permission can replace the current
  Policy object with a new one by invoking
  Policy.setPolicy() and passing a reference to the new
  Policy object. Class loaders consult the
  Policy object to help
  them to decide what privileges to grant code as they import the code into the virtual machine.
  A security policy is a mapping from a set of properties that characterize running code to the permissions
  granted the code. In the 1.2 security infrastructure, the properties that characterize running code are
  collectively called the code source. A code source is represented by a
  java.security.CodeSource object, which contains a
  java.net.URL to represent the codebase and an array of zero or more certificate
  objects to represent the signers. Certificate objects are instances of subclasses of the abstract class
  java.security.cert.Certificate. A
  Certificate is
  an abstraction that represents a binding of a principal to a public key, and another principal (the certificate
  authority mentioned previously) that vouches for that binding. The
  CodeSource
  object contains an array of
  Certificate objects, because the same code can be
  signed (vouched for) by more than one party. The signatures are usually obtained from a JAR file.
  All of the tools and access control infrastructure that accompanies the concrete
  SecurityManager in version 1.2 work only with certificates. None work with bare
  public keys. If you don't have a certificate authority handy, you can sign your own public key with your
  private key and generate a self-signed certificate. The
  keytool program from the Java
  2 SDK version 1.2 always generates a self-signed certificate when it generates keys. In the code signing
  example given earlier in this chapter, for instance, the
  keytool created not only
  public/private key pairs, but also self-signed certificates for the aliases
  friend and
  stranger.
  A permission is represented by an instance of a subclass of the abstract class
  java.security.Permission. A permission object has three properties: a type, a
  name, and an optional action. A permission's type is indicated by the name of the permission class. Some
  examples of permission types are:
  java.io.FilePermission,
  java.net.SocketPermission, and
  java.awt.AWTPermission. A permission's name is encapsulated inside the
  Permission object. For example, the name of a
  FilePermission might be:
  "/my/finances.dat"; the name
  of an
  SocketPermission might be
  "applets.artima.com:2000"; and the name of an
  AWTPermission might be
  "showWindowWithoutBannerWarning". The third property of a
  Permission object is its action. Not all permissions have an action. An example of
  an action for a
  FilePermission is: "
  read,write", and for a
  SocketPermission is: "
  accept,connect". A
  FilePermission with the name
  /my/finances.dat and
  action
  read,write represents permission to read and write to the file
  /my/finances.dat. Both name and action are represented by
  Strings.
  The Java API has a large hierarchy of permissions that represent potentially dangerous actions code
  may wish to take. You can also create your own permission classes to represent custom permissions that
  you use for your own purposes. For example, you could create permission classes that represent permission
  to access particular records of your proprietary database. Defining custom permission classes is one way
  you can extend the 1.2 security mechanism to reflect your own needs. If you create your own
  Permission classes, you can use them like any of the built-in
  Permission classes from the Java API.
  In the
  Policy object, each
  CodeSource is associated with
  one or more
  Permission objects. The
  Permission objects
  with which a
  CodeSource is associated are encapsulated in an instance of a subclass
  of
  java.security.PermissionCollection. Class loaders can invoke
  Policy.getPolicy() to get a reference to the policy object currently in effect.
  They can then invoke
  getPermissions() on the
  Policy
  object, passing in a
  CodeSource to get a
  PermissionCollection of
  Permission objects for the
  passed
  CodeSource. A class loader can then use the
  PermissionCollection retrieved from the
  Policy object to
  help it decide what permissions the code it is about to import will be granted.
  java.security.Policy is an abstract class. One of the implementation
  details of concrete
  Policy subclasses is how an instance of the subclass learns what
  the policy should be. Subclasses can take various approaches, such as deserializing a previously serialized
  policy object, extracting the policy from a database, or reading the policy from a file. The concrete policy
  subclass supplied by Sun with the version 1.2 Java Platform takes the latter approach: it enables you to
  express your security policy in a context free grammar in an ASCII policy file.
  A policy file is consists of a series grant clauses, each of which grants a code source a set
  of permissions. As mentioned previously, a code source consists of a codebase, which is a URL from which
  code was loaded, and a set of signers. In the policy file, signers are designated with the alias with which the
  signer's public key is stored in a keystore file. The keystore can be explicitly specified in the policy file in a
  keystore statement.
  As an example of a policy file, consider the
  policyfile.txt file from the
  security/ex2 directory of the CD-ROM:
  keystore "ijvmkeys"; grant signedBy "friend" { permission java.io.FilePermission "question.txt", "read"; permission java.io.FilePermission "answer.txt", "read"; }; grant signedBy "stranger" { permission java.io.FilePermission "question.txt", "read"; }; grant codeBase "file:${com.artima.ijvm.cdrom.home}/security/ex2/*" { permission java.io.FilePermission "question.txt", "read"; permission java.io.FilePermission "answer.txt", "read"; };
  The first statement in the
  policyfile.txt file is a
  keystore statement:
  keystore "ijvmkeys";
  This keystore statement indicates that the key aliases mentioned in the rest of the policy file refer to
  certificates stored in a file named
  "ijvmkeys". Because this filename includes no
  path, the file must be located in the current directory -- the directory in which the Java application using this
  policy file is started.
  The second statement in the policy file is a grant statement:
  grant signedBy "friend" { permission java.io.FilePermission "question.txt", "read"; permission java.io.FilePermission "answer.txt", "read"; };
  This statement grants two permissions to any code signed by the entity with the alias
  "
  friend". The granted permissions are: permission to read a file named
  question.txt and permission to read a file named
  answer.txt. Because these filenames appear with no path, both files must be in the
  current directory, the directory in which the application is started. Because no codebase is mentioned in the
  grant clause, code signed by
  friend can come from any codebase. All code signed by
  friend, regardless of codebase, will be awarded permission to read
  question.txt and
  answer.txt.
  The third statement in
  policyfile.txt is another grant statement, similar in
  form to the first:
  grant signedBy "stranger" { permission java.io.FilePermission "question.txt", "read"; };
  This statement grants one permission to any code signed by the entity with the alias
  "
  stranger": permission to read a file named
  question.txt.
  This file must be sitting in the current directory, the directory in which the application is started. Because no
  codebase is mentioned in the grant clause, code signed by
  stranger can come from
  any codebase and will still be awarded permission to read
  question.txt. Note that
  although
  stranger is allowed to read the question contained in
  question.txt,
  stranger is not allowed to peek at the answer
  contained in
  answer.txt. This contrasts with the privileges awarded to
  friend, who is allowed to read both the question and the answer.
  The fourth and final statement in this policy file is yet another grant statement:
  grant codeBase "file:${com.artima.ijvm.cdrom.home}/security/ex2/*" { permission java.io.FilePermission "question.txt", "read"; permission java.io.FilePermission "answer.txt", "read"; };
  This final grant statement grants two permissions to any code that was loaded from a particular
  directory, permission to read a file named
  question.txt and permission to read a
  file named
  answer.txt. Both files must be in the current directory, the directory in
  which the application is started. Note that this grant statement does not mention any signers. The code can
  be signed by anyone or no one. So long as it is loaded from the indicated directory, the code will be granted
  the listed permissions.
  The codebase URL in this grant statement takes the form of a
  file: URL that
  includes a property,
  ${com.artima.ijvm.cdrom.home}. If you run the
  AccessControl example programs described later in this chapter, you'll have to set
  the
  com.artima.ijvm.cdrom.home property to the path of the CD-ROM that
  comes with this book, or to whatever directory you have moved the
  security
  subdirectory from the CD-ROM. The
  Policy object that is instantiated based on the
  contents of
  policyfile.txt will take the
  com.artima.ijvm.cdrom.home property into account when it constructs the
  URL for the
  CodeSource for this grant clause.
  As class loaders load types into the Java virtual machine, they assign each type into a protection domain. A protection domain defines all the permissions that are granted to a particular code source. (A protection domain corresponds to one or more grant clauses in a policy file.) Each type loaded into a Java virtual machine belongs to one and only one protection domain.
  The class loader knows the codebase and the signers of any class or interface it loads. It uses that
  information to create a
  CodeSource object. It passes the
  CodeSource object to the
  getPermissions() method of the
  currently in-force
  Policy object to get an instance of a subclass of the abstract class
  java.security.PermissionCollection. The
  PermissionCollection holds references to all
  Permission
  objects granted to the given code source by the current policy. With both the
  CodeSource that it created and the
  PermissionCollection
  it got from the
  Policy object, it can instantiate a new
  ProtectionDomain object. It places the code into a protection domain by passing
  the appropriate
  ProtectionDomain object to the
  defineClass() method, an instance method of class
  ClassLoader that user-defined class loaders call to import type data into the Java
  virtual machine. This assigning classes into protection domains is a critical job which, as mentioned earlier
  in this chapter, is one of three ways the class loader architecture supports Java's sandbox security model.
  Although the
  Policy object represents a global mapping from code sources to
  permissions, in the end the class loader is the responsible party that decides what permissions code is going
  to get when it runs. A class loader could, for example, completely ignore the current policy and just assign
  permissions off the cuff. Or, a class loader could add permissions to those returned by the policy object's
  getPermissions() method. For example, a class loader for loading applet code
  could add a permission to make a socket connection back to the host from which the applet came to the
  permissions, if any, granted to the code by the policy. As you can see, the class loader plays a crucial
  security role as it loads classes.
  For a graphical depiction of protection domains, code sources, and permissions, consider Figure 3-5. In
  Figure 3-5, the method area and heap are shown after the code inside
  friend.jar is
  loaded under the policy defined by
  policyfile.txt.
  friend.jar is a JAR file in the
  security/ex2/jars
  directory of the CD-ROM, and
  policyfile.txt is an ASCII policy file in the
  security/ex2 directory. The
  friend.jar file contains two
  class files,
  Friend.class and
  Friend$1.class. As
  described in the code signing example earlier in this chapter, both of these class files have been signed by
  friend. When these classes are defined by the class loader, they are placed into a
  protection domain whose
  CodeSource object indicates two things. First, the
  CodeSource indicates that the class files were loaded from a local jar file, whose
  URL is:. And second, the
  CodeSource indicates that the class files were signed by
  friend, an alias associated with a certificate in the local keystore. The
  ProtectionDomain object encapsulates a reference to the
  CodeSource object and to a
  java.security.Permissions object.
  java.security.Permissions, a concrete subclass of the abstract
  java.security.PermissionCollection class, represents a heterogeneous
  collection of permissions. The
  Permissions object holds references to two
  java.io.FilePermission objects. These two
  FilePermissions grant the privilege to read files named
  question.txt and
  answer.txt in the current directory.
  When a class loader imported
  Friend and
  Friend$1 into the method area shown in Figure 3-5,
  the class loader passed a reference to the
  ProtectionDomain object to
  defineClass() along with
  the bytes of the class files. The
  defineClass() method associated the type data in the method area for
  Friend and
  Friend$1 with the passed
  ProtectionDomain object. This association
  is shown graphically in Figure 3-5, which includes arrows that represent references to the
  ProtectionDomain
  object held as part of the type data in the method area for
  Friend and
  Friend$1.
  Class
  java.security.AccessController provides a default security
  policy enforcement mechanism that uses stack inspection to determine whether potentially unsafe actions
  should be permitted. The access controller can't be instantiated. It isn't an object. Rather, it is a bundle of
  static methods collected in a single class. The central method of the
  AccessController is its static
  checkPermission()
  method, which decides whether or not a particular action is allowed. This method returns
  void and takes a reference to a
  Permission object as its only
  parameter. Similar to the check methods of the security manager, if the
  AccessController decides the operation should be allowed, its
  checkPermission() method simply returns silently. But if the
  AccessController decides that an operation should be forbidden, its
  checkPermission() method completes abruptly by throwing an
  AccessControlException, or one of its subclasses.
  As mentioned previously, the concrete
  SecurityManager's implementation of
  the legacy check methods (such as
  checkRead() and
  checkWrite()) simply instantiate an appropriate
  Permission
  object and invoke the concrete
  SecurityManager's
  checkPermission() method. The concrete
  SecurityManager's checkPermission() method simply
  invokes checkPermission() on the
  AccessController.
  Thus, if you install the concrete
  SecurityManager, the
  AccessController is the ultimate entity that decides whether or not potentially
  unsafe actions will be allowed.
  The basic algorithm implemented by the
  AccessController's
  checkPermission() method makes certain that every frame on the call stack has
  permission to perform the potentially unsafe action. Each stack frame represents some method that has been
  invoked by the current thread. Each method is defined in some class. Each class belongs to some protection
  domain. And each protection domain contains a set of permissions. Thus, each stack frame is indirectly
  associated with a set of permissions. For an action represented by the
  Permission
  object passed to the
  AccessController's
  checkPermission() method to be allowed, the basic algorithm of the
  AccessController requires that the permissions associated with each frame on the
  call stack must include or imply the
  Permission passed to
  checkPermission().
  The
  AccessController's
  checkPermission()
  method inspects the stack from the top down. As soon as it encounters a frame that doesn't have permission,
  it throws an
  AccessControlException. By throwing the exception, the
  AccessController, indicates that the action should not be allowed. On the other
  hand, if the
  checkPermission() method reaches the bottom of the stack without
  encountering any frames that don't have permission to perform the potentially unsafe action,
  checkPermission() returns silently. By returning rather than throwing an
  exception, the
  AccessController indicates the action should be allowed
  The actual algorithm implemented by the
  AccessController's
  checkPermission() method is a bit more complex than the basic algorithm
  described here. By invoking any of several
  doPrivileged() methods of class
  AccessController, programs can in effect cause the
  AccessController to stop its frame by frame search before it reaches the bottom
  of the stack. The
  doPrivileged() method will be described later in this chapter.
  implies()Method
  To determine whether or not the action represented by the
  Permission object
  passed to the
  AccessController's
  checkPermission()
  method is included among (or implied by) the permissions associated with the code on the call stack, the
  AccessController makes use of an important method named
  implies(). The
  implies() method is declared in class
  Permission, as well as in classes
  PermissionCollection
  and
  ProtectionDomain.
  implies() takes a
  Permission object as its only parameter and returns a boolean
  true or
  false. The
  implies() method of
  class
  Permission determines whether the permission represented by one
  Permission object is naturally implied by the permission represented by a different
  Permission object. The
  implies() methods of
  PermissionCollection and
  ProtectionDomain
  determine whether the passed
  Permission is included among or implied by the
  collection of
  Permission objects encapsulated in the
  PermissionCollection or
  ProtectionDomain.
  For example, a permission to read all files in the
  /tmp directory would naturally
  imply a permission to read
  /tmp/f, a specific file in the
  /tmp
  directory, but not vice versa. If you asked a
  FilePermission object that represents
  the permission to read any file in the
  /tmp directory if it implies the permission to read
  file
  /tmp/f, the
  implies() method should return
  true. But if you ask a
  FilePermission object representing the
  permission to read
  /tmp/f if it implies the permission to read any file in the
  /tmp directory, the
  implies() method should return
  false.
  The
  Example1 application from the
  security/ex1
  directory of the CD-ROM demonstrates this meaning of
  implies():
  import java.security.Permission; import java.io.FilePermission; import java.io.File; // On CD-ROM in file security/ex1/Example1.java class Example1 { public static void main(String[] args) { char sep = File.separatorChar; // Read permission for "/tmp/f" Permission file = new FilePermission( sep + "tmp" + sep + "f", "read"); // Read permission for "/tmp/*", which // means all files in the /tmp directory // (but not any files in subdirectories // of /tmp) Permission star = new FilePermission( sep + "tmp" + sep + "*", "read"); boolean starImpliesFile = star.implies(file); boolean fileImpliesStar = file.implies(star); // Prints "Star implies file = true" System.out.println("Star implies file = " + starImpliesFile); // Prints "File implies star = false" System.out.println("File implies star = " + fileImpliesStar); } }
  The
  Example1 application creates two
  FilePermission
  objects, one that represents read permission for a particular directory and another that represents read
  permission for a particular file in that same directory. The
  FilePermission object
  referenced from local variable
  star represents permission to read any file in
  /tmp. The
  FilePermission object referenced from local
  variable
  file represents permission to read file
  /tmp/f. When
  executed, this application prints:
  Star implies file = true File implies star = false
  The
  implies() method is used by the
  AccessController to determine whether a thread has permission to take actions. If
  the
  checkPermission() method of the
  AccessController
  is invoked to determine whether that thread has permission to read file
  /tmp/f, for
  example, the
  AccessController can invoke the
  implies()
  method on the
  ProtectionDomain objects associated with each frame of that
  thread's call stack. To each
  implies() method, the
  AccessController can pass the
  FilePermission object
  representing permission to read file
  /tmp/f that was passed to its
  checkPermission() method. The
  implies() method of
  each
  ProtectionDomain object can invoke
  implies() on
  the
  PermissionCollection it encapsulates, passing along the same
  FilePermission. Each
  PermissionCollection can in
  turn invoke
  implies() on the
  Permission objects it contains,
  once again passing along a reference to the same
  FilePermission object. As soon
  as a
  PermissionCollection's
  implies() method
  encounters one
  Permission object whose
  implies() method
  returns
  true, the
  PermissionCollection's
  implies() method returns
  true. Only if none of the
  implies() methods of the
  Permission objects contained in a
  PermissionCollection return
  true does the
  PermissionCollection return
  false. The
  ProtectionDomain's
  implies() method simply returns what
  the
  PermissionCollection's
  implies() method returns. If
  the
  AccessController gets back a
  true from the
  implies() method of a
  ProtectionDomain associated with
  a particular stack frame, the code represented by that stack frame has permission to perform the potentially
  unsafe action.
  The next few sections give several examples to illustrate the manner in which the
  AccessController performs stack inspection. In the upcoming examples, code
  signed by both
  friend and
  stranger will be trusted to some
  extent, but
  friend code will be trusted more than stranger
  code. In particular, code signed by both
  friend and
  stranger
  will be given permission to read a file named
  question.txt, which contains a
  question. But although code signed by
  friend will be given permission to read a file
  named
  answer.txt, which contains the answer to the question asked in
  question.txt, code signed by
  stranger will not. These
  permissions granted to
  friend and
  stranger are those outlined
  in the
  policyfile.txt file from the
  security/ex2
  directory of the CD-ROM, which was described earlier in this chapter. Each of the upcoming examples will
  take their policy from
  policyfile.txt.
  The stack inspection examples all make use of classes that implement the
  Doer
  interface:
  // On CD-ROM in file // security/ex2/com/artima/security/doer/Doer.java package com.artima.security.doer; public interface Doer { void doYourThing(); }
  To be a
  Doer, a class must provide an implementation for one method:
  doYourThing(). Classes that implement
  Doer can do whatever
  they feel like in their
  doYourThing() method. For example, here's a class that
  implements
  Doer named
  TextFileDisplayer whose "thing"
  is to display the contents of a text file:
  // On CD-ROM in file security/ex2/TextFileDisplayer.java import com.artima.security.doer.Doer; import java.io.FileReader; import java.io.CharArrayWriter; import java.io.IOException; public class TextFileDisplayer implements Doer { private String fileName; public TextFileDisplayer(String fileName) { this.fileName = fileName; } public void doYourThing() { try { FileReader fr = new FileReader(fileName); try { CharArrayWriter caw = new CharArrayWriter(); int c; while ((c = fr.read()) != -1) { caw.write(c); } System.out.println(caw.toString()); } catch (IOException e) { } finally { try { fr.close(); } catch (IOException e) { } } } catch (IOException e) { } } }
  When you create a
  TextFileDisplayer object, you must pass a file path name
  to its constructor. The
  TextFileDisplayer constructor stores the passed path
  name in the
  filename instance variable. When you invoke
  doYourThing() on the
  TextFileDisplayer object, it will
  attempt to open and read the contents of the file and print them at the standard output.
  Another example of a
  doYourThing() method comes from classes
  Friend and
  Stranger, which appeared earlier in this chapter in
  the code signing example and are shown again here to refresh your memory:
  //Controll