This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
Publisher Stacy L. Hiquet Marketing Manager Heather Hurley Managing Editor Sandy Doell Project Editor/Copy Editor Kezia Endsley Technical Reviewer NIIT Interior Layout Marian Hartsough Associates Editorial Assistants Margaret Bauer Elizabeth Barrett Indexer Tom Dinse Cover Designer Mike Tanamachi
About NIIT NIIT is a global IT solutions corporation with a presence in 38 countries. With its unique business model and technology-creation capabilities, NIIT delivers software and learning solutions to more than 1,000 clients across the world. The success of NIIT’s training solutions lies in its unique approach to education. NIIT’s Knowledge Solutions Business conceives, researches, and develops all of its course material. A rigorous instructional design methodology is followed to create engaging and compelling course content. NIIT trains over 200,000 executives and learners each year in information technology areas using stand-up training, video-aided instruction, computer-based training (CBT), and Internet-based training (IBT). NIIT has been featured in the Guinness Book of World Records for the largest number of learners trained in one year! NIIT has developed over 10,000 hours of instructor-led training (ILT) and over 3,000 hours of Internet-based training and computer-based training. IDC ranked NIIT among the Top 15 IT training providers globally for the year 2000. Through the innovative use of training methods and its commitment to research and development, NIIT has been in the forefront of computer education and training for the past 20 years. Quality has been the prime focus at NIIT. Most of the processes are ISO-9001 certified. It was the 12th company in the world to be assessed at Level 5 of SEI-CMM. NIIT’s Content (Learning Material) Development facility is the first in the world to be assessed at this highest maturity level. NIIT has strategic partnerships with companies such as Computer Associates, IBM, Microsoft, Oracle, and Sun Microsystems.
Acknowledgments My husband and my parents have been a strong support to me while I worked at long hours to complete this book. They really helped me bring out the best in the book. I thank them all for their support. My project manager, Anita Sastry, has worked meticulously reviewing and giving here valuable inputs to the book. Without her help, the book would not have been in its present form. Thank you Kezia Endsley for editing the book so well. Your valuable inputs on the book make it a wonderful book! I would also like to thank Stacy Hiquet for making this book happen at the first place! She has provided active support in all development stages of the book. My special thanks also go out to Pallavi Jain for helping me out with reviews of the book and giving valuable inputs for improving the quality of the book.
About the Author HARPREET SETHI has worked at NIIT since 1999 as a technical trainer, technical writer, instructional designer, and an ID reviewer. She has two years of experience in teaching various career programs at NIIT’s Career Education Group division. Harpreet has taught various technical subject areas, including Java, C, C++, HTML, UML, Networking Essentials, SQL Server 6.5, Unix, Microsoft Windows NT Server 4.0, Windows 95, Windows 98, and Microsoft Office 97 and 2000. Harpreet has worked extensively as an instructional designer and as a developer with NIIT’s Knowledge Solutions Business division to develop and review instructor-led training (ILT) products for various software and technologies. These include Java Programming Essentials, Macromedia Dreamweaver 2.0, 3.0, and 4.0, Microsoft Office 2000, Microsoft Office XP, Adobe Photoshop 6.0, and StarOffice 5.1. She has worked on projects for various U.S.-based clients on these technologies.
Introduction This book provides you with comprehensive knowledge about Java security. It is aimed at readers who are proficient in Java. This book is intended for Web application developers, whose job profile includes setting up security, monitoring, security maintenance, and other advanced management and maintenance tasks. This book will be of immense help for both novice and experienced developers who want to efficiently handle their role. It is for anyone who wants to create secure Java applications or applets. This book includes detailed explanatory concepts, hands-on exercises, and questions to check your understanding, at the end of each chapter. This book has three parts. The first part covers the basic Java security features. This part starts off with a general overview and need for Java security. The book covers a comparison of the Java 2 security model with the security model for earlier versions. JVM architecture and security features are discussed next, in this book. This part also covers the vulnerabilities of Java class file format. The second part covers the advanced Java security features. This part covers the various security components, such as class loader, class file verifier, and security manager in detail. The various Java 2 security tools, such as keytool, jar, jarsigner, and Policy tool are also discussed. This part also introduces the concept of cryptography. The cryptographic features in Java are supported by the Java Cryptography Architecture ( JCA) and Java Cryptography Extension ( JCE). The Java Authentication and Authorization Service ( JAAS) is discussed later in the book. Apart from the chapters, this book also includes another section containing the appendixes. The appendixes include FAQs and tips and tricks that give you real-life advice about Java security. This section also covers the future of Java security in further releases of Java. Although the Java 2 security model is comprehensive, it still needs to be improved to avoid implementation errors caused due to the presence of security loopholes.
How to Use This Book This book has been organized to facilitate a better grasp of content covered in the book. The various conventions used in the book include the following: ◆ Notes. Notes give additional information that may be of interest to the
reader, but it is not essential to performing the task at hand.
xx
Introduction ◆ Cautions. Cautions are used to warn users of possible disastrous results if
they perform a task incorrectly. ◆ A “Check Your Understanding” section at the end of each chapter. These
sections contain multiple choice and short answer questions that help you review the material discussed in that chapter as well as determine whether you understand the material well enough to proceed.
PART
I
Introduction to Java Security
This page intentionally left blank
Chapter 1 Security: An Overview
I
n this chapter, I’ll discuss the importance of security while downloading executable content from the Web. The chapter starts with a brief introduction of the Java development and execution environment. I’ll also discuss the Java security features as well as the limitations in Java security. The security implementation in applets and applications will also be discussed. The big question is why all this hype about security; why is security implementation necessary while designing applications? The next section discusses the need for security.
The Need for Security The Internet has become a major business platform for various companies and organizations. Companies have attributed their growth rate to the Internet, which acts as a platform for marketing, sales, advertising, providing services, and customer support. However, the Internet brings with it certain risks as well. There are times when a Web server is taken over or the private data submitted to a site is distributed across malicious sites. Firewalls are violated, and viruses are sent across networks, leading to the disruption of network operations. Therefore, if it is necessary for the Web platform to survive in the current business scenario, it is important to use this platform securely. Java, being the programming language for the Web, is also expected to impart security to Web applications. With the new and emerging technologies in the current scenario, the security aspect continues to be ignored in the initial phases of development. This is in contrast to Java, in which security is the primary design goal. I consider that fortunate because Java is primarily being used to design Web applications and applets. Although the initial Java security model was quite simple, there have been various evolutionary enhancements to this model. Therefore, if you wish to design distributed and secure Web applications, Java is the way to go. However, acquiring high-level security is not all that easy considering the fact that we hear about security threats every second day. High-level security requires elaborate planning, a structured design, strong architecture, and picture-perfect implementation. All these prerequisites form a part of the Java 2 platform and you will be reading about most of them in this book. Moreover, Java requires a security-oriented development and execution environment. Therefore, in the next section, I will discuss the development and execution environment of Java 2 before going into the intricacies of Java security.
SECURITY: AN OVERVIEW
Chapter 1
Java Fundamentals Java is composed of three components, namely, the development environment, the execution environment, and the interfaces. The Java 2 SDK has a lot more to offer than other programming languages. The SDK contains the necessary code and tools to compile, debug, and execute your Java programs. In addition, it supports the architecture for networking, file I/O, and creating GUI applications. These features of Java comprise the development environment. The Java Virtual Machine ( JVM) implements the execution environment of Java. Java programs can be executed on any platform, but the execution environment should be JVM-enabled. Therefore, it is because of the JVM that Java programs need not be rewritten if they are to be implemented on different platforms. The JVM is also responsible for security implementation. The JVM is a part of the Java Runtime Environment ( JRE), which also contains the Java core classes. The JRE exclusively contains runtime components such as the JVM and the core Java classes. It does not contain any development tools such as the compiler or the debugger. Java requires interfaces to interact with non-Java applications. These interfaces might have a simple or complex architecture. For example, you need the JDBC interface to connect to a relational database from a Java application. An interface can be used to serve as a protocol and can be implemented by any class anywhere irrespective of the class hierarchy. Interfaces are used when you don’t need to build a relationship between unrelated classes, although you do need to share the similarities between classes. Interfaces are used to identify the methods that classes could implement. The subsequent sections will cover these components in detail.
An Overview of the Development Environment Once you have written the Java source code, you need to compile it. Java both compiles and interprets the source code. The Java source code is compiled by using the javac compiler. After the Java source code is compiled, it is converted to bytecode, which is a platform-independent format. This is in contrast to other high-level languages in which the source code is compiled and converted to the machine language format. Now, the bytecode is interpreted by the interpreter, which is a part of the JVM. Java is an object-oriented language. Therefore, the Java source code consists of classes containing data and methods. When the source code is compiled into bytecode, the resulting class file will contain only those methods and classes, which are specified in the class definition. In addition, the source code contains calls to the methods of different classes. These classes are dynamically loaded by the JVM during runtime. This
5
6
Part I
INTRODUCTION TO JAVA SECURITY
is called dynamic linking. Therefore, while the program is running, the JVM builds an interconnection between the different classes and interfaces being used in the program. The system classes in Java are arranged in the form of packages. Packages are collections of related classes. You can also create your own packages containing classes that are derived from the Java basic classes. Next, you can control access to the members of a class through other objects. This is necessary to impart security to secret information or the data stored in classes. There are access levels specified in Java to regulate the access control on the members of a class. These access levels are mainly of three types: public, private, and protected. They are implemented by declaring fields or methods of classes by using the public, private, and protected keywords. The keywords are discussed as follows: ◆ The most secure access is provided by the private keyword. The class
members defined by using this keyword are accessible only from within the class that contains them. Therefore, private members are reserved only for the class that contains them and cannot be accessed by other classes. ◆ The next access level specifier is protected. The class members defined by using the protected keyword are accessible by the class that contains them,
the subclasses, and the remaining classes in the package. Therefore, protected members can be used by related classes such as the subclasses of the class that contains these protected members. However, unrelated classes cannot use them. ◆ The class members marked with the public access level specifier can be
accessed by any class irrespective of the package in which they are contained. You should be careful while declaring class members as public. Only those members that do not affect the running program by affecting the state of the object should be declared as public. The Java development environment also comprises Visual Application Builders (VABs) and Java beans. The next section covers these concepts in detail.
Java Beans The primary use of Java beans is that they facilitate the creation of visual applications. You might have used application builders such as Visual Basic, Visual Age, or Delphi. The main purpose of using application builders is that they reduce the code complexity. They also reduce the code needed for the application core, which is used for linking components. Therefore, VABs are used to build complex applications through the linking of predefined components. The advantage of using these builders is that you need not write complex code for your application. All you need to do is pick up a component with the desired functionality and plug it with the application. A Java
SECURITY: AN OVERVIEW
Chapter 1
bean is a reusable software component that can be modified by using the Visual builder tools. Reusable software components can be compared to the reusable parts used in other industries. For example, the electronic components of the circuit boards or car parts that can be reused across various car models are reusable components. Toolkits, calendars, and button sets are examples of reusable components in the software industry. Reusable components can act as standard interfaces for builder tools. Reusable components such as Java beans support a design-time interface, which allows builder tools to know the properties and events supported by the Java beans components. The attributes and behaviors of Java beans are marked through signature patterns that are referred to by the application construction tools. However, the use of Java beans is not limited to these tools because the signature patterns are recognizable through human readers and other application builder tools as well. Therefore, a Java bean is a component that consists of Java classes and performs a specific function. Although Java beans vary in functionality, they support certain basic features that are stated here: ◆ Java beans support signatures that allow a builder tool to examine the func-
tionality of the specified bean. This process is also referred to as introspection. ◆ The properties and behaviors of Java beans can be customized or modified
as per the user need. This process is also referred to as customization. ◆ The properties of Java beans can be modified or set programmatically while
either designing the application or running it. ◆ Java beans fire events, thereby informing the builder tools about their func-
tionality. ◆ Java beans support persistence, whereby the beans are customized and their
changed state is saved and reloaded later. Therefore, Java beans are reusable components that can be used within applications and customized as per your need. However, they affect the security aspect of your application because your application now contains code that is not being written directly by you. For this reason, you need to be sure that these components are from a trusted source before using them in your applications. The Java development environment also consists of security tools to handle security in your applications. The next section gives a brief introduction of these tools.
Java 2 Security Tools The Java 2 SDK supports various security tools to implement security-related features. These are the jar, jarsigner, keytool, and Policy tools. Except the Policy tool, which has a GUI interface, the remaining three are command-line tools. The jar
7
8
Part I
INTRODUCTION TO JAVA SECURITY
tool was also available in jdk1.1. The tools, jarsigner and keytool, replace the javakey tool of jdk1.1. These two tools provide more security features than the javakey tool. They password-protect the keystore and the private keys and generate and verify signed Java ARchive ( JAR) files. ◆ The jar tool is used to create JAR files. The JAR file format can be com-
pared to a ZIP file format. Multiple files such as class files are stored in a single archive file. To digitally sign code, the jar tool is used to place the code in a JAR file and the jarsigner tool is used to sign this file. ◆ The jarsigner tool is used to sign JAR files and verify the signatures on
JAR files. The tool refers to the keystore to find the private key and the associated certificates for signing the JAR files. The keystore is created by using keytool. Because the keystore and the private keys are password protected, the jarsigner tool prompts for passwords when accessing them. ◆ The keytool is used to manage the keystores. A keystore is a protected data-
base that contains the keys and certificates for a particular enterprise. Access to the keystore and the private keys is password protected. The keytool is used to create public and private key pairs, issue certificate requests, fetch replies from these requests, and assign the public keys of other parties as trusted. You will read more about these tools in Chapter 9, “An Introduction to the Java 2 Security Tools”. Let’s move on to the subsequent section, which discusses the execution environment in detail.
An Overview of the Execution Environment The execution environment of Java mainly comprises of the JVM. You are already aware that the Java source code is both compiled and interpreted. After compilation, the Java source code is converted to bytecode, which is further interpreted by the JVM. Therefore, the JVM converts the bytecode to the machine language code while executing the Java program. The execution time for a Java program is faster than that of other interpreted programs because the JVM interprets bytecode, which is nearly in the machine language format. The Java source code is compiled only once, whereas it is interpreted every time the Java program is executed. However, the JVM implementation does not result in high performance. The Just In Time (JIT) compilation is used to implement the JVM, and thereby, increase the performance of Java programs. In the JIT compilation, the interpretation part is ignored and the bytecode is directly converted to optimized machine code and executed directly. This results in faster execution time. Therefore, JIT compilation removes the overhead of interpretation.
SECURITY: AN OVERVIEW
Chapter 1
Besides the interpretation of bytecode, the JVM also implements security in Java applications. Before interpretation, the environment in which the program will execute is set by the JVM. Security implementation is mainly handled at this time. It is handled by three components, namely the class loader, the class file verifier, and the security manager. The subsequent sections discuss the role of these components in detail.
The Class Loader Dynamic class loading is an important feature of the JVM because it provides the capability of loading components at runtime. The class loader is one of the key components in the JVM. A class loader is a Java class with a specified functionality. It is responsible for the effective loading of the Java class bytecode into the runtime environment. The class loader primarily performs four tasks: ◆ It loads the Java class bytecode from an input stream, namely a file or a net-
work location. ◆ It invokes the bytecode verifier to verify the loaded class bytecode. ◆ It then invokes the access controller and security manager to confirm the
access permissions to the resources. ◆ Finally, it passes the classes to the JRE.
In the JRE, things may not be totally under your control, because the source from where the class bytecode is loaded can vary. Therefore, the class loader plays an important role in implementing security by fetching class files and creating a class object based on the access control permissions specified in the security policy file. Programmers can also specify their own class loaders that identify the location from where the classes are loaded and assign security permissions to them as per the requirement. An application can have multiple instances of ClassLoader objects, each for a specific class type. Alternatively, applets cannot create customized class loaders. However, a browser can load multiple applets for different Web pages using separate class loaders. These applet classes are treated distinctly by the JVM. The root of the class loader class hierarchy is the java.lang.ClassLoader class. This class was present in jdk1.0. However, in Java 2, a subclass of the abstract ClassLoader class is created, SecureClassLoader. The SecureClassLoader class further implements security by determining the access permissions for the class in the client system. You will read more about class loaders and their functionality in Chapter 5, “The Class Loader and the Class File Verifier”. Let’s move on to the next section, which discusses the class file verifier.
9
10
Part I
INTRODUCTION TO JAVA SECURITY
The Class File Verifier Before the class loader loads the specified class into the JRE, it passes the loaded bytecode to the class file verifier, which is also known as the bytecode verifier. You might be wondering why the bytecode generated by the Java compiler needs to go through a verification phase before being loaded to the JRE. This is because of the concept of trusted and untrusted classes. All the classes belonging to the Java core and those installed on the local system are trusted classes and, hence, need not be verified before execution. The remaining classes that are referenced by the concerned class are untrusted and need to be checked by the bytecode verifier. It is the task of the class loader to, in turn, fetch these classes for the execution of the Java program. Let’s run through the entire process once again. The JVM loads classes during runtime, the default class loader loads the core and local classes (the trusted classes), the referenced classes (the untrusted classes) are fetched from the specific URLs, and these external classes need to be verified by the class file verifier. This verification is necessary for confirming that only secure and valid code is executed. The untrusted code is checked before execution, resulting in uninterrupted execution of Java programs. Therefore, the role of the class file verifier is important for security implementation in the system. Figure 1-1 depicts this process. A class file verifier works in two distinct phases. The first phase is after the class is loaded, when the class file verifier checks the internal integrity of the bytecode and structure of the class files. In the second phase, when the bytecode is actually executed, the class file verifier checks the existence of the referenced classes in the respective code.
FIGURE 1-1 Role of the class file verifier
SECURITY: AN OVERVIEW
Chapter 1
You will read more about class file verifiers and their functionality in Chapter 5, “The Class Loader and the Class File Verifier”. Let’s move on to the next section, which discusses the security manager.
The Security Manager The security manager is also involved in the secure loading and execution of Java programs. The main task of the security manager is to judge whether access requests for valuable system resources are to be allowed. The security manager first considers the Java class requesting for the resource. The concerned Java class passes the control to the core Java classes, which, in turn, request for the resource. If the request is permitted, the program execution proceeds, otherwise java.lang.SecurityException is thrown. For every JVM implementation, only a single instance of the SecurityManager class can be created. The JVM can be so configured that the security manager is set before the first applet is loaded in the browser. This removes the possibility of the security manager being replaced by a malicious Java applet, which, in turn, replaces the security manager with its own security manager that has access permissions for valuable system resources. In earlier versions of Java, creating a customized security manager was not possible because these versions supported the SecurityManager class, which was an abstract class. However, in Java 2, the SecurityManager class is not abstract, and instances or subclasses of this class can be created in which methods can be added or replaced in the default SecurityManager class implementation. You will read more about the working of the security manager in Java 2 in Chapter 6, “Java 2 Security Manager”. Figure 1-2 depicts the role of the three components—the class loader, the class file verifier, and the security manager—in the execution environment. Now, as we have discussed the two components of Java, the development and the execution environment, let’s discuss the third component— interfaces.
Java Interfaces Java interfaces are used so that unrelated objects can interact with each other. Interfaces are nothing but behavior protocols that can be implemented by any class in the class hierarchy. Therefore, interfaces provide the facility of extending Java classes and interact with different types of applications. With the advent of Java technology and the increase in the complexity of Java projects, the need for such interaction has increased.
11
12
Part I
INTRODUCTION TO JAVA SECURITY
FIGURE 1-2 Components of the JVM in the execution environment
You can also extend Java by using methods written in some other programming languages. However, these methods do not provide the basic capabilities such as portability, security, and flexibility that are provided by the Java language and are ignored by a Java perfectionist. Java applets act as interfaces for client applications. However, these applications also need to interact with outdated systems for data and information. Therefore, with Java 2, there have been enhancements to the interfaces and architectures so that applications can be written purely in Java and yet interact with the outside world. The most used Java interfaces are JDBC, Remote Method Invocation (RMI), and Java beans. Let’s discuss these technologies with respect to the security loopholes in each of them.
JDBC JDBC provides connectivity to databases from Java applications or applets. You need to take care of the security implementation while providing access to a database through JDBC. Therefore, you should keep security in mind while designing server
SECURITY: AN OVERVIEW
Chapter 1
applications by providing limited access to the Java applets accessing the underlying database. Not all SQL queries should be allowed to execute on the database.
Remote Method Invocation (RMI) Remote Method Invocation (RMI) enables you to create distributed Java applications in which the methods of a remote Java object on a system can be called by another JVM on another system. However, there are security concerns when you deal with RMI. For example, when a server-side application communicates with a client-side applet for data that needs to be acquired through a database, the server-side application needs to verify the requests before giving access to the applet for the database.
Java Beans Java beans, as discussed previously, provide easier application development. They are also a useful aid in distributed application development. However, you need to be cautious of the security implementation, as is the case with the other interfaces discussed above. A malicious Java applet can access the trusted classes through the Java beans that provide access to particular executable files. Therefore, you need to be very careful when dealing with Java beans. Now, as I have discussed the basics of the Java environment, let’s discuss the security aspect of Java as well. The subsequent sections in this chapter will cover this important topic.
An Overview of Java and Security Java is a language for the Web and is used to design Web pages. The first Web pages created using Java contained just images and text. However, this was an advancement from the traditional way of presenting information. You had access to a large repository of data that could be accessed through the hyperlinks on a page. The only drawback was that for executable programs, you had to refer to the server. The Web interface was so designed that you filled up data in a page and the data was transmitted to the server where it was acted on by an executable program. There were animations or puzzles that made a Web page more exciting; however, they could not be successful if they were placed on the server for execution. Moreover, if the concerned program was placed on the server, there was a long time interval between the submission of a customer query and the result of the query being displayed on the Web page. Wouldn’t it be much easier and require less overhead if executable programs could be placed on the Web page itself? However, here comes the security factor. If programs are placed on Web pages, there is a risk of viruses being downloaded when
13
14
Part I
INTRODUCTION TO JAVA SECURITY
you load the specified Web page in the browser or execute the program on your computer. Second, if the program was created using a specific platform that is not supported by your system, the main benefit of the Web being platform-independent whereby you can run programs on any client is lost. This security aspect is generally ignored by most programming languages. It is generally assumed that applications written in the specific language will consider security in its implementation details. However, Sun considered security from the design stages. Therefore, Java has many more capabilities besides being just a programming language. Java is an object-oriented language. However, certain areas, such as pointers that are a part of most object-oriented languages, are avoided in Java. This is because pointers can be used by malicious code to access the restricted memory areas of a client system. The JVM is an aid towards security implementation in Java. The executable code downloaded to the system does not have access to the valuable system resources of the client system. The access requirements of the code are judged by the security policies, the protection domains, and the security managers. You will read about each one of these in later chapters of the book. Due to the features discussed previously, the concept of executable code seems real now. You may already be familiar with the fact that Java supports both applications and applets. The applications are loaded locally, and the applets are Java programs that are downloaded from the Web and are executed in the browser. In earlier versions of Java, the applications could access all the local resources of the system, whereas the applets were constrained to the sandbox. However, in Java 2, all the code is recognized by its source and the entities signing the code. Access to system resources is judged by the security policy. There is a default system policy; however, you can also create a security policy file that suits your requirements. You can actually decide which resource is needed by the program and to what extent by deciding the access permissions for the resource in the policy file.
Java Security Requirements You can further strengthen Java security by knowing the security loopholes well before and trying to fill them up. First, you need to understand the scope of security for the system as a whole. You need to judge the threshold of attack by a cracker. The course taken by the data in an application needs to be judged for any security loopholes. While downloading applets from the Internet or any shared network, you need to consider network and data protection. Moreover, the code needs to be authenticated for eligible users through digital signatures. Network protection can be controlled using firewalls and security policies, whereas for data protection, you can encrypt the data by using various data encryption techniques. You need to be clear about the sys-
SECURITY: AN OVERVIEW
Chapter 1
tem areas that are susceptible to attacks by crackers. You then need to strengthen these areas and secure them accordingly. Don’t panic! This may sound like a whole lot of groundwork on your part; however, Java 2 is here to help you out. The Java 2 security architecture has the capability to predetermine the areas that are to be acted on by the local or remote code. You can then control access to these areas by specifying permissions for them through security policy files, protection domains, and security managers. There may still be loopholes in the Java security implementation. These are generally reported by applet hunters, people downloading applets or professionals who wish to find the loopholes and close them. Therefore, the risk in Java security is reduced as and when these loopholes are found and closed. You need to respond to the implementers when you locate any loopholes in the security implementation of Java. Therefore, to implement high-level Java security, you need to install and review Java security continuously.
Java Security On the whole, the major differences between Java and C++ do not permit the implementation of malicious code either while designing or creating Java programs. The security aspect of Java is highlighted through the use of the JVM. The security of the JVM, in turn, is attributed to bytecode rather than the Java source code. Bytecode, being machine-independent, enables you to run the applet from any server. Therefore, the platform-independence and security aspects of the Java programming language can be attributed more to bytecode than the Java source code. Bytecode is derived from the compilation of the Java source code, or it can also be generated from other high-level languages. However, bytecode derived directly from high-level languages has limited security implementation and can be used by crackers to create programs directly in bytecode, which can be used to attack systems. Let’s discuss the various security benefits in Java in the next section.
Java Security Features Code distribution is another area where security can be at a risk. This is the case when applications need code running on both the server and client ends. For example, this occurs while interacting with a GUI or while connecting to the network using a remote caller. Such an open link can be a security loophole. Therefore, the system is susceptible to an attack through the intermediary placed between the client and the server. The role of this intermediary is to intercept the data flowing between the two
15
16
Part I
INTRODUCTION TO JAVA SECURITY
computers. So, unknowingly, while you are transferring data from the client to the server or vice versa and this data is passing through the intermediary, the cracker can attack the intermediary and the data can either be corrupted, changed, or infected with a virus. Therefore, code distribution between a client and a server can aid the tampering of the system. Therefore, instead of transferring code using an intermediary computer, you can do it through a physical medium such as a disk, by downloading code over a trusted network connection, or by using the Java security features. Java may not always be the fastest or the correct choice; however, Java code is much more secure than a normal download in the sense that it can be checked and verified through digital certificates and signatures. This does not hold true for the other possibilities in physical distribution. The three components of the JVM—namely, the class loader, the class file verifier, and the security manager—help to perform checks on the downloaded applets. You have already read about the role of each component in the preceding sections. The task of the class loader is to accumulate all the necessary resources for the execution of a Java program; a class file verifier checks and verifies the loaded code; and finally, the security manager dictates the access control permissions for system resources. Therefore, the security features of Java distinguish it from other programming languages. Java users can secure their systems from attacks that lead to system corruption. They do this through digital signatures and by using the three security components to validate the code. If you miss out these validation checks, you could end up crashing your system or being attacked by a virus. Now, let’s discuss the limitations of Java security in the next section.
Limitations of Java Security If the Java system is set up properly, there is no reason for security threats in the system. If implementation errors are avoided, the threats to Java security can be minimized. However, if something does go wrong in the implementation, the most tragic security threat is that of your system crashing due to attack of malicious applets. If you are connected to another computer via a network, there is a possibility of data being transferred to the other computer or a virus attack. Therefore, Java is designed to effectively combat these security threats and act as a savior. Java can also provide security against the invasion of highly secret business data. For example, imagine that your private key has been deciphered and used by crackers to digitally sign code or official documents using your identity. This is much worse than having to reinstall your system or rewrite your business data on the computer. Because Java is the only language in which security has been implemented right from the design stage, it offers a high level of security to remote executable programs. It is
SECURITY: AN OVERVIEW
Chapter 1
not that there are no security loopholes in Java. However, Java security professionals have taken it as a challenge to locate these loopholes in Java implementation and close them as soon as possible. However, there are still certain security threats that cannot be minimized even by using Java. An example of such a threat is cycle stealing. Imagine that you have downloaded a program and executed it on your computer. Now, instead of executing, it has blocked the major system resources and is wasting the CPU time of the client computer, thereby, preventing the actual operation of the program to be performed. Therefore, Java still has threats attached to its security implementation, although they are not a major problem for the client system. Now, let’s move on to discussing security in Java 2.
Java 2 Security Through the preceding sections, it might be clear that security implementation is now an important part of Java. This is true because Java is a language of the Web and needs to be secure. The Java security implementation has changed across the various version releases of Java. In jdk1.0, local code was granted access to all the system resources, whereas remote or downloaded code was allowed access to very limited resources. Therefore, the use of applets or downloaded code was limited to the enhancement of Web pages. In jdk1.0, local code was totally trusted and given access to all the system resources. In jdk1.1, developers had the option of digitally signing their code. Depending on these signatures, the user of the client computer could decide whether the downloaded code was from a trusted source. If the code belonged to a trusted source, it was given access to all the system resources. In other words, this trusted code was equated with the local code on the client computer. Alternatively, code that was not from a trusted source was subjected to limitations and could access only limited system resources. The only drawback in this model was that you could not control the permissions given to trusted signed code. For example, if you wanted to restrain the remote code from having write permission on a file, it was not possible because, by default, trusted remote code had write permission for all the files on the system. The remote code was automatically granted permissions to access, read, and write in the files and directories of the system or start a network connection. This limitation has been, however, controlled in the next version release, Java 2. In Java 2, you can further classify remote code on the basis of the source location of the code or the owners of the code who have digitally signed the code. You can regulate control over resource access depending on the URL location of the code and the
17
18
Part I
INTRODUCTION TO JAVA SECURITY
signers of the code. For example, a user can specify that the code signed by particular signers is to have only read access to a specific file on the system and that downloaded from a specific URL location can have write permission. These restrictions are not only for remote code. Even local code is subjected to them. Therefore, with careful understanding of the security threat, Java could easily pave its way through effective security implementation. You know that Java programs are of two types, applets and applications. The subsequent sections cover applet and application security in Java 2.
Java 2 Applet Security With the concept of executable programs being introduced on the Web, more emphasis should be paid to applet security as a whole. A minor security flaw in an executing applet can result in a system crash, a virus attack, or the disclosure of sensitive data in a database to crackers. Therefore, for users to effectively use applets, it is important to minimize these security threats. However, one cannot ignore the fact that increasing the security of applets curbs their capabilities. The Java 2 security model allows you to utilize the various capabilities of applets as well as maintain a high level of security. You are already familiar with the various security restrictions imposed across various versions. The most significant security feature introduced in Java 2 is the capability to specify security policies for both applets and applications. The Java 2 fine-grained access control model contains a configurable security policy that caters to both Web developers and users. Developers can provide applet capability to their users, and users can limit these capabilities depending on the trust level of the source location on the Java code. Therefore, the security model of jdk1.2 provides the least privileges for system resources. The security policy determines which resource access is permitted to an applet based on the source of the code and the identities of the signers of the code. You can specify certain policies in the security policy without programming. For example, you can grant permissions to any applet to listen to TCP ports greater than 1000 or you can grant the applets signed by Cod and John permission to write files into the temp directory on the hard disk.
Specifying a Security Policy for an Applet To specify a security policy for an applet, all you have to do is to edit the appropriate policy configuration file. You can do this in a number of ways, which are listed here: ◆ Set the value of the policy.java property to the name of the appropriate
security policy file.
SECURITY: AN OVERVIEW
Chapter 1
◆ Use the -D command-line option to set the value of the java.policy prop-
erty to a different security policy file. Consider the following code statement: java -Djava.policy = “try.policy” Trial
This code statement enables you to use the try.policy file while executing the Trial applet class. ◆ Create or edit the default system policy file located at \lib\security\java.policy where is the directory in which the default jdk1.2 installation is stored. When you edit the java.policy file, it applies to all
the users of the jdk1.2 installation. You will learn to compose a policy file in a subsequent section of the chapter. ◆ Finally, you can replace the class, java.security.PolicyFile, used to
implement the security policy with another class. This can be done by editing the java.security file. All you need to do is change policy.provider = java.security.PolicyFile
to policy.provider = Class1
where Class1 is the class name. When the bytecode is interpreted, it first loads the system policy and then acts on the user policy. The security policy file consists of entries called grant entries, which identify the code’s access permissions on various system resources. The permissions are granted based on the code source and the signers of the code. You will learn more about the security policy file and its entries in Chapter 2, “Java Security Model”.
Signing Applets When an organization or individual signs a specific applet, it indicates that the applet has been reviewed for all security defects. A digital signature attached to an applet acknowledges the fact that the signed code belongs to the specified organization signing the code and has been delivered to the user without any modifications. The organization signing the applet is also responsible in case of security attacks. If the signature is from a known organization, the user can also consider increasing the access level for the specified applet. You need to perform the following two steps to sign applets: ◆ Archive the class files of an applet in a JAR file by using the jar tool. ◆ Sign the JAR file by using the jarsigner tool.
The subsequent sections discuss these steps in detail.
19
20
Part I
INTRODUCTION TO JAVA SECURITY
Creating JAR Files To create JAR files, you first need to move to the directory containing the class files and then execute the jar command. For example, consider the following code statement: jar -cf classes.jar AccessFileApplet.class ReadFileApplet.class
This statement creates the classes.jar file, which contains the classes AccessFileApplet.class and ReadFileApplet.class. You can also verify the classes.jar archive file for its contents by executing the following statement. jar -tf classes.jar
When this command is executed, it lists the class files in the JAR file. However, to use the classes.jar file, you need to specify the ARCHIVE property in the APPLET tag of the respective htm file of the specified applet. Look at the following sample APPLET tag: <APPLET CODE=”AccessFileApplet.class” ARCHIVE=”classes.jar” HEIGHT = 300 WIDTH = 600>
Signing JAR Files You need a keystore and a signer’s key pair before signing the JAR files. A keystore is a database of private keys and their associated certificates. It is used for authenticating the corresponding public keys of the private key. The key pair consists of a public key and an associated private key. A key pair can be generated by using the keytool command with the -genkey option. Figure 1-3 depicts the command prompt window with the keytool command executed. Go through the information that is prompted on execution of the command.
FIGURE 1-3 Execution of the keytool command
SECURITY: AN OVERVIEW
Chapter 1
Therefore, the public and private key pair is created, which associates the public key with the certificate of the signer. The alias name for the certificate by default is <mykey>. The private key and its associated certificates are stored in a .keystore file located in the user’s home directory. Once the keystore and the certificate are created, the JAR file can be signed by using the private key of the signer. This is done by using the jarsigner command. Consider the following code statement: jarsigner classes.jar mykey
This command, when executed on the command line, asks the user for the keystore and the private key password. The jarsigner utility first opens up the JAR files, and then adds the signature-related information, and finally archives the JAR file again.
Using the Policy Tool You can create or edit a policy file by using the text editor or the Policy tool. However, using the Policy tool reduces typing errors and time. It is also easier as compared to a text editor because you need not know the syntax of policy files. Let’s discuss the various steps involved in creation of user policy files. First, you need to type the command policytool at the command prompt. If the Policy tool cannot locate a policy file, it displays a suitable message and a blank Policy Tool window, as in Figure 1-4.
FIGURE 1-4 The Policy Tool window
21
22
Part I
INTRODUCTION TO JAVA SECURITY
To add new policy entries, you need to click the Add Policy Entry button in the Policy Tool window. Let’s imagine that you need to grant all the classes in the C:/Trial directory read permission for the file myFile in the C:\Data directory. On clicking the Add Policy Entry button, the Policy Entry window appears as shown in Figure 1-5. In the CodeBase text box, you’ll enter the URL of the classes, that is C:/Trial. The SignedBy text box is used for signing the code. I’ll leave it blank in this example. The next step involves adding permissions to the specified Policy entry. This can be done by clicking the Add Permission button, which opens the Permissions dialog box, as shown in Figure 1-6.
FIGURE 1-5 The Policy Entry window
FIGURE 1-6 The Permissions dialog box
SECURITY: AN OVERVIEW
Chapter 1
FIGURE 1-7 The changed Permissions dialog box
From the Permission drop-down list, select FilePermission. In the text box to the right of the Target Name drop-down list, type C:\Data\myFile. Finally, from the Actions drop-down list, select read. The Permission dialog box now looks like Figure 1-7. You’ll click the OK button to add the new permission to the policy entry. The new permission appears in the Policy Entry dialog box, as in Figure 1-8. You can click the Done button, as your task is now accomplished. The Policy Tool window now contains a CodeBase value, as shown in Figure 1-9. You can similarly edit and remove policy entries from the policy files.
FIGURE 1-8 The new permission in the Policy Entry dialog
box
23
24
Part I
INTRODUCTION TO JAVA SECURITY
FIGURE 1-9 The Policy Tool window with the CodeBase value
Specifying a Keystore by Using the Policy Tool You can also specify keystore entries by using the Policy tool. For example, imagine that you need to grant code from the URL http://java.sun.com/ and signed by the alias name “marc” permission to initiate socket connections to any hosts. To fulfill this requirement, you need to specify the keystore containing the specified alias name and create policy entries for the specified permission. First, let’s specify the keystore containing key information about the alias. To do this, in the Policy Tool window, choose Edit, Change KeyStore. This opens the Keystore dialog box where you specify the URL and the type of the keystore. Assume that the keystore named newKeystore is stored in the Keystore directory in C:. You’ll type this URL in the New KeyStore URL text box. The keystore type can be taken as the proprietary keystore type supported by Sun, which is “JKS”. Therefore, you’ll enter the value, JKS, in the New KeyStore Type text box. The Keystore dialog box now looks similar to Figure 1-10. Now, let’s add a policy entry with a SignedBy alias. You’ll click the Add Policy Entry button in the Policy Tool window. This displays the Policy Entry dialog box. In the CodeBase text box, you’ll enter http://java.sun.com/*. The asterisk (*) signifies all
SECURITY: AN OVERVIEW
Chapter 1
FIGURE 1-10 The Keystore dialog box
class and JAR files in the specified directory. In the SignedBy text box, you’ll enter marc. To add the specified permission, click the Add Permission button. The Permissions dialog box appears. From the Permission drop-down list, select SocketPermission. In the text box to the right of the Target Name drop-down list, type *. The * represents all hosts. From the Actions drop-down list, select connect. All these operations help you to specify permissions to make connections to all hosts. The Permissions dialog will look like Figure 1-11. Click the OK button and the new permission appears in the Policy Entry window, as shown in Figure 1-12. Click the Done button in the Policy Entry window. The Policy Tool window appears displaying a line representing the policy entry containing the CodeBase and SignedBy values. Figure 1-13 displays the Policy Tool window as discussed.
FIGURE 1-11 The Permissions dialog box
25
26
Part I
INTRODUCTION TO JAVA SECURITY
FIGURE 1-12 The Policy Entry window
FIGURE 1-13 The Policy Tool window
You can save this user-defined policy file, by choosing the File, Save As command in the Policy Tool window. This displays the Save As dialog box, which is set to the user’s home directory. You can type the desired file name and click the Save button. Now, let’s discuss application security in Java 2.
SECURITY: AN OVERVIEW
Chapter 1
Java 2 Application Security In earlier versions of Java, there were no security restrictions imposed on Java applications because they were treated as the local code. Therefore, the local code had full access to most of the system resources. However, in Java 2, applications are also limited by the permissions specified in the security policy. What was the need to limit applications by these permissions? For example, assume that you have received a Java application either by mail or on a disk. The job of the application is to read a file and print it on the screen page-wise. However, you cannot trust the application completely as you have not gone through the source code. Imagine that when you execute the application, the application reads the file, and then it opens a secret socket connection and transfers data from the file to another system on the network. What do you do? According to you, the application was intended to read the file and print its content on the screen. The malicious application can go to the extent of attacking your computer with viruses or crashing your system. Therefore, in Java 2, local applications are also controlled through policy files and have limited access to system resources When you run an application in Java 2, the security manager is not installed automatically. Therefore, by default, the application has full access to system resources. However, you can invoke the security manager by using the -Djava.security.manager attribute. Once the security manager is invoked, the application will be limited by the same access permissions as for the remote code. Consider the following code: javac ReadWriteFile.java java ReadWriteFile
These statements execute the application without the security manager and, hence, the application has full access to system resources. The following code statement invokes the security manager and restricts the application to accessing limited system resources. java -Djava.security.manager ReadWriteFile
In earlier times, secret information was written in code language and decoded by the person for whom it was intended. This technique was known as cryptography and has been implemented as a security feature in Java. Read on to the subsequent section that contains details of Java 2 and cryptography.
Java 2 and Cryptography The meaning of the word, cryptography, is secret writing or hiding secret data from users and enabling only an authenticated user’s access to this data. The main purpose
27
28
Part I
INTRODUCTION TO JAVA SECURITY
of cryptography is to protect the confidentiality of data in a Java application. Cryptography refers to the process of converting data or code to cryptographic material. This process takes a stream of input data or code and converts it into encrypted format. The original input data is called clear text, whereas the encrypted form of data is called cipher text. This encrypted data can be either stored on the computer or transmitted over the network without the risk of it being misused. The cryptography feature was introduced in jdk1.1 through the Java Cryptography Architecture ( JCA), which specified the various cryptographic tools available. In Java 2, the JCA comprises of the cryptography core classes and a package called Java Cryptography Extension ( JCE), which supports encryption in Java 2. I will discuss the JCA in the subsequent section.
Cryptographic Architecture The JCA provides a basic framework for providing cryptographic functionality in Java. The cryptographic functionality refers to protecting data using basic cryptographic functions and algorithms. The JCA also includes signature-generation algorithms for signing data and code. APIs for handling keys and certificates are also a part of the JCA. The JCA is also called the provider architecture. Its design enables the separation of the cryptographic design from the implementation. Therefore, vendors have the independence of specifying their customized implementation for the various tools and APIs supported by the JCA. The classes supported by JCA are called engine classes. These classes represent the various cryptographic functions. You may have different implementation designs for these classes. For example, there are various standards for digital signatures. However, there is a single engine class for signatures even if there are variations in the design of digital signatures. Alternatively, there are provider classes, which are offered by different vendors to implement the varied digital signature algorithms. It is possible for a Java application or applet to create its own digital signatures by using the JCA. Therefore, based on the JCA, now a developer can write more complicated programs to exploit the capabilities of applets and remove the remote applet code from the traditional sandbox limitations. However, the user needs to be convinced that the applet belongs to a trustworthy source and is signed by a trusted entity. Therefore, the signer of the code attaches a public key certificate with the code, which enables the user to check the validity of the signature. In jdk1.1, there was no support for keys and access control lists. However, in Java 2, this support has been provided. The JRE comes with a provider named SUN, which provides implementation algorithms for digital signatures and message digests. There are various cryptographic tools available, which will be discussed in the subsequent section.
SECURITY: AN OVERVIEW
Chapter 1
Cryptographic Tools There are various tools available to implement cryptography, which mainly focus on encryption. The basic tools fall into the three main categories: ◆ Hashing ◆ Bulk encryption ◆ Public key encryption
The subsequent sections deal with each type of tool in detail.
Hashing Hashing refers to the process of taking an input stream of data and converting it into a fixed-length digest. The digest generated is, in turn, used for data authentication. The digest is a secure method of transmitting data across the network. If a user reads the transmitted data and deciphers it along with the digest, he/she will not be able to modify the original data. Therefore, the data can be authenticated at the receiver’s end because it would contain the same digest as the original data piece.
Bulk Encryption Bulk encryption is also known as secret writing and is used to secure large chunks of data. Data Encryption Algorithm (DEA) and Data Encryption Standard (DES) are common algorithms for encryption. The basic design of these algorithms is that they use a key to jumble or encrypt data, and then, the same key has to be used to decipher or encrypt this data.
Public Key Encryption Public key encryption, instead of using a single key as discussed in bulk encryption, uses a key pair for encrypting and decrypting data. This key pair consists of a public key and a private key. The public key is distributed to available users, whereas the private key is kept secret. The methodology followed in this encryption technique is that if a key is used to encrypt data, the data can be decrypted using the second key in the pair and vice versa. This encryption technique is asymmetric in comparison to the bulk encryption technique that is symmetric. It is called asymmetric because the key used for encryption and decryption is different. When data is to be transmitted across a secure connection, the sender can use the public key to encrypt data for the receiver, who, in turn, can decrypt data with the simultaneous private key. Theoretically, if you have either of the keys, you can decipher the second in the pair because they are bound by a defined mathematical relationship. However, practically,
29
30
Part I
INTRODUCTION TO JAVA SECURITY
this is impossible because the size of the keys is quite large and it would require a very high effort on the cracker’s part. Therefore, public key encryption is more secure than bulk encryption. However, the public key encryption technique is expensive and, therefore, should not be used when a large amount of data is to be transmitted over a secure connection. Public key encryption can also be used with bulk encryption. In the SSL protocol, the sender and the receiver agree upon a common key while using public key encryption. Therefore, now, only the two parties know about the key and, hence, bulk encryption can be used to transmit data from one end to another. However, the key needs to be decided afresh for every new connection to be started between the sender and the receiver. There are certain limitations or rules for encrypting data. The next section deals with these rules.
Rules for Encryption Java 2 includes engine and provider classes for digital signatures. However, it does not contain classes for bulk or public key encryption. This is due to the limitations imposed by the United States government on cryptographic technology. If these limitations were not imposed, the National Security Agency (NSA) could not decipher messages to check for communication security while transmitting messages across governments and criminal organizations. Therefore, this agency can control the cipher strength of any message. The cipher strength is controlled by the key size used in the encryption algorithm. However, the United States government has relaxed the export rules. Therefore, now, any cipher strength can be exported but it needs to be associated with a key recovery process, which enables the NSA to decipher the key if they suspect a particular piece of code being transmitted. Keeping these rules in mind, JavaSoft created two cryptographic packages for Java. The part of JCA that can be exported is the one that contains tools for signatures, and the part that cannot be exported is the JCE that contains the general encryption rules and regulations. JCE provides support for message authentication codes (MACs), keys, and ciphers.
Summary In this chapter, I discussed the need for security and the role of Java in implementing security in applications and applets. The chapter started with a brief introduction of the Java development and execution environment. I also discussed interfaces and packages in Java 2. Then, I discussed the Java security features and the limitations in Java security. A comparison of Java security across various versions was presented in brief. I discussed Java 2 security features and the security implementation in Java 2 applets and applications. The final section of the chapter focused on the role of cryptography in the Java 2 security implementation. The JCA was discussed along with
SECURITY: AN OVERVIEW
Chapter 1
the various cryptographic tools. Finally, the rules specified by the United States government on the usage of encryption were discussed.
Check Your Understanding Multiple Choice Questions 1. Which of the following are Java 2 security tools? a. jar b. jarsigner c. keytool d. Java beans 2. Which of the following is a reusable software component that can be modified by using the Visual builder tools? a. Packages b. Interfaces c. Java beans d. JDBC 3. Which of the following are Java interfaces? a. RMI b. Class loader c. JDBC d. Security manager 4. Which of the following tools do you need to sign applets? a. jar b. jarsigner c. Policy d. keytool 5. Which of the following refers to the process of taking an input stream of data and converting it into a fixed-length digest? a. Public key encryption b. Hashing c. Bulk encryption d. Signing
31
32
Part I
INTRODUCTION TO JAVA SECURITY
Short Questions 1. How do you specify a security policy for an applet? 2. Explain the process of signing JAR files. 3. How do you implement security in Java 2 applications?
Answers Multiple Choice Answers 1. a, b, and c. The Java security tools are the jar, jarsigner, keytool, and Policy tools. 2. c. Java beans is a reusable software component that can be modified by using the Visual builder tools. 3. a and c. The most used Java interfaces are JDBC, RMI, and Java beans. 4. a and b. First, you need to archive the class files of an applet in a JAR file by using the jar tool. Then, sign the JAR file by using the jarsigner tool. 5. b. Hashing refers to the process of taking an input stream of data and converting it into a fixed-length digest.
Short Answers 1. To specify a security policy for an applet, all you have to do is to edit the appropriate policy configuration file. You can do this in a number of ways, which are listed here: • Set the value of the policy.java property to the name of the appropriate security policy file. • Use the -D command-line option to set the value of the java.policy property to a different security policy file. Consider the following code statement: java -Djava.policy = “try.policy” Trial
This code statement enables you to use the try.policy file while executing the Trial applet class. • Create or edit the default system policy file located at \lib\security\java.policy where is the directory in which the default
SECURITY: AN OVERVIEW
Chapter 1
jdk1.2 installation is stored. When you edit the java.policy file, it applies to all the users of the jdk1.2 installation. • You can replace the class, java.security.PolicyFile, used to implement the security policy with another class. You do so by editing the java.security file. All you need to do is change the following code statement policy.provider = java.security.PolicyFile
to policy.provider = Class1
where Class1 is the class name. When the bytecode is interpreted, it first loads the system policy and then acts on the user policy. The security policy file consists of entries called grant entries, which identify the code’s access permissions on various system resources. The permissions are granted based on the code source and the signers of the code. 2. You need a keystore and a signer’s key pair before signing the JAR files. A keystore is a database of private keys and their associated certificates. It is used for authenticating the corresponding public keys of the private key. The key pair consists of a public key and an associated private key. A key pair can be generated by using the keytool command with the -genkey option. Therefore, the public and private key pair is created, which associates the public key with the certificate of the signer. The alias name for the certificate by default is <mykey>. The private key and its associated certificates are stored in a .keystore file located in the user’s home directory. Once the keystore and the certificate are created, the JAR file can be signed by using the private key of the signer. This is done by using the jarsigner command. Consider the following code statement: jarsigner classes.jar mykey
This command, when executed on the command line, asks the user for the keystore and the private key password. The jarsigner utility first opens up the JAR files, and then adds the signature-related information, and finally archives the JAR file again. 3. When you run an application in Java 2, the security manager is not installed automatically. Therefore, by default, the application has full access to system resources. However, you can invoke the security manager by using the -
33
34
Part I
INTRODUCTION TO JAVA SECURITY
Djava.security.manager attribute. Once the security manager is invoked,
the application will be limited by the same access permissions as for the remote code. Consider the following code: javac ReadWriteFile.java java ReadWriteFile
These statements execute the application without the security manager and, hence, the application has full access to system resources. The following code statement invokes the security manager and restricts the application to accessing limited system resources. java -Djava.security.manager ReadWriteFile
Chapter 2 Java Security Model
I
n Chapter 1, “Security: An Overview”, you learned that Java is a secure language and there are no deviations from this fact as long as the Java system is configured correctly. The built-in security features of Java improve the overall security of your system. This chapter discusses the Java 2 security model, which provides a way to build secure and distributed applications in Java. The chapter begins with a discussion of security history in the various versions of Java and proceeds to discuss the security features of Java 2 in brief.
Java Security Features Java is being widely used in Internet commerce. This requires the usage of Java applets that allow downloading of remote code into a Web browser. Originally, Java was widely used because of its support of applets. Applets enhanced the capabilities of Web browsers so that they could support the execution of downloaded applications. In addition to downloading remote code to the Web browser, the code was automatically updated when you visited the site from which the code was downloaded. This saved the effort of upgrading versions of any application stored locally on your computer each time a new version was introduced. However, this capability was restricted because of network performance and JVM support in Web browsers. The usage of remote code entailed security threats as well. Therefore, security was never sidelined in the case of Java. It became a primary consideration from the very beginning. When you install and execute desktop applications on your computer, many of these applications need access to the local file system. The same could hold true for Java applets that bring remote applications to your computer. Imagine a Java applet from a malicious site attempting to access your local file system. Now did that scare you? Do not panic because from the very beginning Sun has handled this security part quite well. In addition to the risk discussed previously, remote code can also affect system performance by reducing CPU usage or memory utilization. Remote code can also bring in virus attacks. In addition to applets, Java developers create stand-alone enterprise applications as well. Multiple clients on a network access these applications. Therefore, security considerations are not limited to applets as was the case in earlier Java Security models. However, the Java 2 security model offers extensive security features for applications as well.
JAVA SECURITY MODEL
Chapter 2
Let’s now discuss a few basic security features of Java. These are listed here: ◆ The sandbox mechanism permits only local code to access all system
resources. The remote code has access to only limited system resources. I’ll discuss the sandbox security model in detail in the subsequent section. ◆ Language features, such as no pointer arithmetic and array bounds checking,
provide security to the memory resource. ◆ Finally, digital signatures mark a personal ownership by the owner of a spe-
cific code. Therefore, you can check the authenticity of the code owner by verifying whether the code was changed after the owner last signed it. Java Runtime Environment ( JRE) also supports Java security. You have already read about the three basic extensions of the development environment in Java in Chapter 1, “Security: An Overview”. The class loader checks the loading mechanism for Java programs. It prevents the replacement of system components in the runtime environment. The class file verifier verifies the formatting of the remote code by checking the bytecode for typed parameters. It also checks the flow of the internal stacks. Finally, the security manager controls runtime access on the applications for I/O operations, class-loading operations, and thread-manipulation operations. These security mechanisms continue adding new features with each new release of the Java language. The next section discusses the evolution history of the security model according to each version release.
History of Java Security Java is a well-known Internet technology because of its various supporting features, such as it being object oriented, architecturally neutral, portable, and dynamic. Java has become a highly preferable language of the present because of it being architecturally neutral and portable. These features enable a Java program to run on any platform that supports a JVM-enabled browser. This enables the distribution of Java programs across various clients as well as increases the security risk. I will now discuss the security models supported by the JDK 1.0, JDK 1.1, and Java 2 SDK in the subsequent sections.
The Sandbox Security Model The sandbox model is supported by the Java version 1.0 platform. This is a very limited security model where resource access is concerned. Figure 2-1 depicts the sandbox model. This model marks the local code as trustworthy and assigns full access to all valuable system resources, such as files and network connections. On the other
37
38
Part I
INTRODUCTION TO JAVA SECURITY
FIGURE 2-1 The sandbox security model
hand, remote code has restricted access to these resources, resulting in limiting the vision of dynamic and downloadable applications. Therefore, remote code is marked as untrustworthy. Java applets cannot exploit their functionality because of being limited to a sandbox. They cannot access files, create network connections, or perform write operations. On the other hand, if remote code is removed from the sandbox limitations, there are chances of malicious attacks on the local computer. Therefore, in both the situations, you cannot exploit the Java applet functionality. However, using the trusted code security model in Java 1.1 can solve this problem. The next section discusses this model.
The Trusted Code Security Model The trusted code security model is supported by the Java 1.1 platform. This model overcomes the flaws encountered in the sandbox model. In this model, the limitations on the remote code were reduced without affecting system resources. This model introduced the concept of signed remote code. Remote code with a digital signature that is trusted by the client can have full access to system resources. Therefore, such signed remote code is treated as local code. If the signed code does not belong to a trustworthy source, it is placed in the sandbox where it does not have access to most of the system resources. For example, while downloading the code signed by Sun, you can treat this code as trusted code. Therefore, this code will be assigned all system resources that are assigned to local code on your system. In other words, you can use this code fearlessly like any other product from Sun. If the signed code does not belong to a trustworthy source, it is placed in the sandbox. Figure 2-2 depicts the trusted code security model. Java 1.1 has the jar tool to pack and deliver remote codes along with their digital signatures. The codes are delivered to the destination in the form of JAR files. JAR files
JAVA SECURITY MODEL
Chapter 2
FIGURE 2-2 The trusted code security model
were introduced in Java to reduce the download time of Java applets to the Web browser. This was possible because of the following reasons: ◆ The JAR file can be compared to a ZIP file format where all the compo-
nents of a Java applet are packaged and delivered as one component. This completes the applet download in a single transaction instead of starting a new transaction for every applet component (class files, images, sound files, and so on). Therefore, it saves download time and increases the download speed for the applet download. ◆ Moreover, the JAR file compresses its components and reduces the size of
the files and thereby increases the download speed. For security purposes, the JAR files can be digitally signed, thus authenticating the remote Java applet code. The javakey tool is used to sign the JAR files. You might be wondering whether JAR files can be used across multiple platforms. Yes, the JAR file format is platform-independent. The jdk 1.1 security API consists of the java.security package, which contains the java.security.acl and java.security.interfaces packages. The java security model for jdk 1.1 consisted mainly of cryptographic functions for Java applications. The model in jdk1.1 also contains APIs for message digests, access control lists, digital signatures, and key management. In the jdk 1.1 security model, the access control for remote code was not well defined. If the remote code contained a trustworthy signature, it would have access to system resources. Or else it would be limited to the sandbox. On the other hand, the local code had blind access to all system resources. However, the Java 2 security model with its fine-grained access control meets the gaps of the earlier versions. The next section covers the Java 2 security model in detail.
39
40
Part I
INTRODUCTION TO JAVA SECURITY
Java 2 Security Model The fine-grained access control in Java 2 provides case-specific security control for remote code. It is not just dependent on the trusted signature attached to the code. Using fine-grained access control, you can provide specific permissions to remote code. For instance, you wish to assign read and write permissions to the file ABC and no permissions to the file XYZ. Depending on the source of the remote code, it is possible to do this through the fine-grained security model. Therefore, developers can specify access permissions depending on their requirements and the source location of the remote code. Now, local and remote code can be confined to resources in a domain according to specific conditions and policies. This is in contrast to the earlier security models where local code was trusted. In the earlier models remote code was not trusted and placed in the sandbox unless it contained a digital signature by an authorized signatory. Moreover, the fine-grained model relieves developers from the overhead of differentiating between local and remote code.
Specifications of Fine-Grained Access Control With the new Java security architecture, you can assign access permissions to system resources, regardless of the fact that the code is local or remote. The permissions are defined depending on what is contained in a policy file. Therefore, the client has rights to decide the permissions to be granted to different sources of code. Now, the users can download applications from the Internet and assign permissions to only specific modules depending on their requirement. At times while downloading code from a site that is considered a trusted source, you are held back by a surprise because the code transmitted a virus to all your e-mail contacts. In the previous versions of Java, you could not control access of the code to system resources in such a way that only specific modules of the code were executed at your computer. However in the case of Java 2, you can specify that the code downloaded from a URL can access only the resources as defined in the policy file. This is possible because the JVM does not assign the restricted system resources to the downloaded code. For example, you can limit the code to have access to only specific files and the code cannot access the remaining files and directories on your local computer. Therefore, with Java 2, you have full access to set permissions for the program actions to be performed on your computer. You can now be very specific about applet actions, defining exactly what an applet is permitted to execute on your computer. Isn’t that great? This type of access control was never possible in the earlier versions of java where the much restricted sandbox security model controlled the JVM resource access. Therefore, applets downloaded in a Java 2-enabled Web browser now have the ability to specify access permissions for susceptible resources on your computer without modifying the browser implementation or the computer platform. This finegrained security model is illustrated in Figure 2-3.
JAVA SECURITY MODEL
Chapter 2
FIGURE 2-3 The fine-grained security model
You might be wondering how this acclaimed fine-grained security model works. What are the security tools and APIs needed to implement this model? The answers to all these queries can be found in the subsequent sections.
Java 2 Security API In addition to the earlier packages contained in the java.security package, certificate interfaces for parsing and managing certificates have been added. This is possible because of two new packages, java.security.cert and java.security.spec, which have been added to the java.security package. The java.security.cert package also provides support to X.509 v3 certificates. The Certificate interface of the java.security package is an interface of abstract methods for managing identity certificates.
NOTE An identity certificate is a guarantee certificate by a principal stating that a public key is that of another principal, where a principal denotes a user, group, or company.
In Java 2, the Certificate interface is deprecated. A new certificate-handling package named java.security.cert is created in Java 2. The java.security.cert package contains the X509Extension interface for X509 extensions. The extensions defined for X509 certificates and crls (Certification Revocation Lists) define methods for relating new attributes with keys and users for managing crl distribution and manage certification hierarchy. The java.security.spec package provides classes and interfaces for key and algorithm parameter specifications. This package contains AlgorithmParameterSpec and
41
42
Part I
INTRODUCTION TO JAVA SECURITY
KeySpec interfaces that contain specifications for cryptographic parameters and for
key material within a cryptographic key. The next section discusses the Java 2 security tools used for authenticating data and maintaining access control on various system resources.
Java 2 Security Tools Java 2 provides four security tools for implementing security: jar, keytool, jarsigner, and the Policy tool. Each of these tools is discussed in the following list in brief. ◆ The jar tool is similar to the one in jdk 1.1, which is used to pack and
deliver remote codes along with their digital signatures. ◆ The keytool performs a series of tasks. It creates pairs for public and private
keys, manages keystores, and generates signed X509 certificates.
NOTE A keystore is a protected database, and it contains private and public keys and certificates. A keystore is protected through a password, and all the private keys contained in a keystore are in turn protected by different passwords. ◆ The jarsigner tool is used to sign JAR files. It also verifies signed JAR
files. This tool accesses the keystore when it signs or verifies a signature in JAR files. It needs a private key to sign a JAR file and a public key while verifying a signature. Therefore, it accesses the keystore for private and public keys. ◆ The Policy tool is used to define the security policy by creating and updating the policy configuration files. The Policy tool utility is started using the policytool command.
From the preceding sections you know that the Java 2 security model contributes its fine-grained access control to the security policy file, protections domains, and code source. I will discuss these concepts in the next section.
Protection and Permissions You are already aware that permissions to access a particular system object are stored in the security policy file. This file can be edited either manually or by the Policy tool. The domain that contains system objects that can be accessed by a principal is called
JAVA SECURITY MODEL
Chapter 2
the protection domain. Principal is the entity to which permissions are granted, and it can access objects in the protection domain depending on the permissions specified for the particular object in the policy file. In addition to the default policy file, you can also have user-defined policy files. However, at a given point in time, the JVM can assign resources based on permissions specified in a single policy file.
NOTE The Policy class of the java.security package contains the refresh() method, which re-reads the policy files and refreshes the changes in them. You need to explicitly call this method because it cannot be called automatically.
In the Java 2 security model, you can assign permissions to either local or remote code. Both local and remote code are trusted if they come from a reliable source and are signed by a trusted signatory. Both these requirements are met by the code source for the code. The code source is defined by the URL location of the code and a list of trusted signers of the code. The java.security.CodeSource class marks the code source for local or remote code. The constructor function of the CodeSource class takes two parameters—the URL location of the downloaded code as the first parameter and the signers of the code as the second parameter. The first parameter is a URL object whereas the second parameter is a Certificate object.
Description of a Policy File A policy file consists of a group of statements called grant entries. These statements identify the permissions granted to both local and remote code based on the code source. The grant entries identify the code source along with the permissions granted to the code source. The syntax of grant entries states that they begin with the grant keyword followed by the optional SignedBy or CodeBase clause and end with a list of permission entries. The syntax of grant entries is as follows: grant [SignedBy “signer_names”] [, CodeBase “URL”] { permission entries};
The SignedBy clause consists of a list of alias names for all signers of the specific code. This clause is optional if the code has not been signed by any entity. In addition alias names are not case-sensitive. Let’s look at a few examples to understand the concept of the SignedBy clause. SignedBy “Johnson” SignedBy “Johnson, Maria, Sam, Meredith”
43
44
Part I
INTRODUCTION TO JAVA SECURITY
The CodeBase clause identifies the URL location of the downloaded code. The example that follows specifies that the code is downloaded from the JohnsieToys Web site. CodeBase “http://www.johnsietoys.com”
You’ll learn more about grant entries in detail in the subsequent sections.
Protection Domains After reading about the code source and policy files, let’s try defining the protection domain according to these concepts. The scope of a protection domain is applicable to the code source and permissions specified in the policy files. Therefore, when a class is loaded, it is mapped to a protection domain depending on its code source. The access permissions of the code are described through the grant entries in the policy file. You might be wondering whether classes having similar permissions are placed in similar protection domains. This again depends on the code’s code source. If the classes have a same set of permissions but belong to different code sources, they are placed in different protection domains. Protection domains are of two types, application and system domains. An application domain constitutes the applets or applications that are given access permissions as defined by the security policy file. On the other hand, the system domain contains all the system code, which is granted all permissions. Therefore a policy file does not control entities of a system domain. After being clear about the basic security mechanisms, let’s see the changes in classes in Java 2 over the previous versions.
Classes in Java 2 In the earlier versions of Java, such as jdk 1.1, the class path default value included the path specifying the directory containing system classes. The system classes were contained in a ZIP file. The class path also included the path of the current working directory. You already know that you can create your own class files as well. These are stored in the classes folder. To add new libraries to the default class path, you can use the java tool, the CLASSPATH environment variables, and the –classpath and –cp flags. If you use the –classpath and –cp flags, the class path contains both the original classes.zip reference and the new classes that you created. This will become clearer in the following code statement: java -classpath C:\jdk1.1.7\lib\classes.zip; \app\classes
JAVA SECURITY MODEL
Chapter 2
On the other hand, if the CLASSPATH environment variable is set, the class path contains the classes.zip file and the current working directory is replaced by the recently assigned values. Therefore, you need to explicitly specify the current working directory while setting the CLASSPATH environment variable. This resulted in discrepancy while setting the class path through either of the two ways discussed previously. This resulted in different versions of classes.zip file being generated. This problem was solved in Java 2 where the -classpath flag and the CLASSPATH environment variable now performed the same task. The only difference from the earlier versions was that now the class path was further split into three separate paths. These paths are explained in the subsequent sections.
The Extensions Mechanism The extensions mechanism is a new feature of Java 2. Extensions are groups of packages and classes used to extend the functionality of the Java platform. This is done by the extension mechanism where the runtime environment finds and loads extension classes without naming them in the class path. Extension classes are used in a similar manner as you use system classes. They are called so because they extend the core API of Java 2. There have been additions to the core API since the various version releases in Java. Therefore, the extensions mechanism extends the functionality of the Java platform without increasing the size of the core API. In Java 2, the process of adding new libraries is now considerably simple. You can skip the usage of the CLASSPATH environment variable or the - classpath command now. When you place the JAR file containing the extensions in the extensions directory, it is added. Such an extension is called an installed extension. On the other hand, a download extension is one in which you refer to a JAR file from another JAR file. Installed extensions are JAR files in the lib/ext directory. However the location of installed extensions can be changed during compile time by using the -extdirs flag followed by the directories separated by semicolons. On the other hand, download extensions include classes and JAR files, which are specified in the Class-Path headers in manifests of other JAR files. Consider the code statement that follows by assuming that new.jar and new1.jar are two files stored in the same directory and the manifest of new.jar contains the following header. Class-Path: new1.jar
Now, the classes in new.jar can invoke the classes in new1.jar without including new1.jar classes in the class path. Classes for downloaded extensions are specific to the application or the applet that uses them. On the other hand, the classes in installed extensions can be used by any code.
45
46
Part I
INTRODUCTION TO JAVA SECURITY
Application Class Path The class path for an application is set by the java.class.path property. The value contained in this property is called the application class path. The application class path is used to specify the application URL search path for loading the classes and resources of the specified application. It is used to find the location of user-defined classes that are not part of extensions or the system classes. The default value of the java.class.path property is marked by the CLASSPATH environment variable or is set to the current directory if the earlier (CLASSPATH) is missing. The class path can be set using the - classpath option or the CLASSPATH environment variable. The - classpath option is much preferred because it can be used to set the class path individually for different applications. In Java 2, classes are located by searching for bootstrap classes, extension classes, and class path in the same order as specified.
System Class Path In the earlier versions of Java, the system classes were stored in ZIP files. However, in Java 2, they are stored in JAR files where rt.jar contains the runtime classes and the tools.jar file contains the tool classes supported by SDK. All these files now are a part of the default installation of Java 2. You no longer need to use the CLASSPATH environment variable or the -classpath command to specify the system classes. Now, the location of the system classes is set automatically during runtime by the sun.boot.class.path variable. In default installation, the boot class path contains the rt.jar file containing the default system class files. The default boot class path can be changed to include another JAR file containing a different version of the system class files by using the bootclasspath compile time flag and the -Xbootclasspath runtime flag. Your understanding will be clearer once you look at the following example: javac -bootclasspath E:\newfiles\new.jar First.java java -Xbootclasspath: E:\newfiles\new.jar First
The first statement sets the boot class path to the new.jar file, thus overriding the default settings. The -bootclasspath flag is used at by compile time whereas the Xbootclasspath variable is used at runtime to set the path to use the new.jar file containing the new system classes. To execute the Java program, it is essential that both the rt.jar file and the application class file be specified for the JVM at runtime. Therefore, you need to explicitly set the boot class path to the rt.jar file containing the default system classes. Consider the following example: java -Xbootclasspath: E:\newfiles First
JAVA SECURITY MODEL
Chapter 2
This statement gives an error because the JVM cannot locate the rt.jar file containing the system classes. It could locate First.java because it was saved in the newfiles directory. The following command executes correctly as it specifies both the rt.jar file and the location of the application class file. java -Xbootclasspath: E:\jdk1.2\jre\lib\rt.jar; E:\newfiles First
In the Java 2 platform, the classes that are trusted are those that are specified in the boot class path. Therefore, you need to explicitly specify the path of the rt.jar file for error-free execution at runtime. After being acquainted with the changes in the class path in Java 2, let’s discuss the Java 2 class loading mechanism, which plays a critical role in imparting security to Java applications and applets.
Java 2 Class Loader As the name suggests, the main task of the class loader is to locate and fetch classes. However, the class loader refers to the policy file and accordingly defines the permissions for the specified class while fulfilling its primary task. In the earlier versions of Java, all local code and trusted remote code were given full access to all system resources. On the other hand, the code that was not trusted was given access to limited system resources. The security manager looks after resource access, and the class loader fetches the classes based on the security permissions specified. Therefore, each application had customized SecurityManager and ClassLoader classes. However, Java 2 has improved these classes. The SecurityManager class can be instantiated, which is in contrast to the earlier versions where it was an abstract class. A new class named SecureClassLoader is created in Java 2, which extends the ClassLoader class. This new class provides an additional support while defining classes with associated code source and permissions as specified in the system policy file.
Working of the SecureClassLoader Class After executing a Java application or applet, an object of the SecureClassLoader class is created. This object locates and loads the class file of the specified Java program. Then, a subclass of the SecurityManager class is created at runtime. Finally, the main() or init() method is called. Therefore, the main task for implementing security in this process is imparted to the SecureClassLoader class, which ensures the safe loading of classes at runtime. The SecureClassLoader class locates classes for the JVM by following the class search path. It begins by looking for class files in the boot class
47
48
Part I
INTRODUCTION TO JAVA SECURITY
path followed by the installed extensions. Finally, the classes specified by the application class path are checked. After the class is located and loaded in the JVM, the SecureClassLoader class specifies a protection domain for the class. Firstly, a code source is created for the specified class by referring to the code base and the signatures attached to the code. Then, the code source fetches the protection domain of the class, which contains the specified permissions for access control of resources at runtime. Therefore, the SecureClassLoader class repeats this process until all classes required to run the program are loaded in the JVM along with their protection domains.
Resource Access Control While executing a Java program, you might need access to various protected resources, such as the file system or network of the local computer or access to files for I/O or other resources defined in the program. It is the SecurityManager class that looks after resource access for running a Java program. The security manager determines whether requests for resources are to be met or not from a security perspective. The security manager’s checkPermission method determines whether a runtime resource access request should be granted or denied. This method takes as an argument a Permission object. If the resource request is permitted, the checkPermission() does not return anything. Or else, a SecurityException is thrown. The checkPermission method with a single permission argument performs security checks within the context of the currently executing thread. Therefore, the checkPermission method verifies that the protection domain of all classes in the current thread include permissions to perform the requested operation. These permissions are specified in policy files. The next section covers the policy files in detail.
Policy Files The policy file containing the access permissions for code from various sources is stored in the lib\security directory. You can also use multiple policy files to specify the overall system policy. However, the default setting includes a single system-wide policy file and a user-defined policy file in the user’s home directory. A policy file contains a list of grant directives. It may also contain a keystore entry along with the grant entries. This is necessary when a grant entry contains a certificate by a signer and these certificates are stored in the keystore.
Grant Directives There are several Permission classes in Java 2. All of these classes are derived from the java.security.Permission class, which is an abstract class. Further classes are derived
JAVA SECURITY MODEL
Chapter 2
from it to represent various accesses, such as I/O or network access. Objects of these permission classes are created by passing two arguments, the resource to be accessed (file name/socket number) and the action to be performed on the resource (open, listen, read, or write). If the target Permission class has no applicable action with it, the second parameter is null in this case. Examples of Permission classes that have actions associated with them are FilePermission and SocketPermission. In addition to the various permission classes in Java 2, there is a special permission class named AllPermission. This class provides access to all system resources. This class should be used intelligently to impart security. It can be used by system administrators who need access to all system resources under a specific set of permissions. This class indicates that you have the required permissions to perform all actions on specified resources. You’ve already read that a policy file contains permissions specified by grant entries, which in turn contains a code source and a list of permissions. Let’s discuss each part of the grant entry in detail.
Code Source The code source consists of two parts, the code base and the digital certificates used to sign the class code. ◆ The code base specifies the URL or location from where the code is down-
loaded. The code base field of a grant entry is optional. When it is omitted, permissions can be granted to code from various sources. The following code statement specifies the code URL marked by the CodeBase keyword. CodeBase “http://www.mybooks.com”
◆ The digital signatures for the code are specified by the SignedBy keyword.
This field of a grant entry is again optional. When it is omitted, permissions can be granted to both signed and unsigned code. Consider the following code statements: SignedBy “Johnson” SignedBy “Johnson, Maria, Sam, Meredith”
The code must be signed by all the signers specified in the SignedBy clause list to be granted permissions. This statement stands true for multiple signers as well.
Permission List The permission list may contain many permission entries. The beginning of the entries in the permission list is marked by the permission keyword. Next, all these entries contain the following parts in common.
49
50
Part I
INTRODUCTION TO JAVA SECURITY
◆ The permission entries must contain the full name of the specified Permission class along with the package. For example, the entry java.io.FilePermission states that the FilePermission class lies in the java.io package. ◆ Next, there is a quoted string value specifying the target for the Permission
class. For example, “E:\\Temp\\MyFile.txt”
This quoted string acts as a target for the FilePermission class where you need permissions for accessing MyFile.txt. However, this field can be omitted for the AllPermission class. ◆ There is another quoted string specifying the actions permitted on the target. For example, the quoted string “read, write, execute” can be actions that you need to perform on the MyFile.txt. All permission classes need
not have actions associated with them. Exceptions to this statement are FilePermission, PropertyPermission, and SocketPermission. ◆ Finally, you need to specify the digital certificates used by the permission classes. These are specified by the SignedBy keyword. This field is optional.
The syntax discussed previously should be followed failing which might result in errors during compile time by the JVM. A single mistake in the policy file might put the security of the entire system at stake. However, the Policy tool prevents errors in manual editing of the default policy file. The following section discusses the default system policy file.
Default System Policy File The default system policy file is stored in the lib/security directory under the java home directory. The default policy file contains the following code: // Standard extensions get all permissions by default grant codeBase “file:${java.home}/lib/ext/*” { permission java.security.AllPermission; };
// default permissions granted to all domains
grant { // Allows any thread to stop itself using the java.lang.Thread.stop()
JAVA SECURITY MODEL
Chapter 2
// method that takes no argument. // Note that this permission is granted by default only to remain // backwards compatible. // It is strongly recommended that you either remove this permission // from this policy file or further restrict it to code sources // that you specify, because Thread.stop() is potentially unsafe. // See “http://java.sun.com/notes” for more information. permission java.lang.RuntimePermission “stopThread”;
// Allows anyone to listen on un-privileged ports permission java.net.SocketPermission “localhost:1024-”, “listen”;
// “Standard” properties that can be read by anyone
The first grant entry specifies the code base URL whereas there are no signers for the code specified. The AllPermission class grants all permissions to JAR files from the Java extensions directory. The second grant entry also does not specify any signers. This grant entry lists the permissions to be granted to the system classes. The next section covers the security management features in Java 2.
Managing Security in Java 2 The security manager is used to manage security in Java 2. Managing security applies both to applications and applets. The main task of the security manager is to perform access control checks depending on the current security policy. When an applet is running, the AppletViewer and the current browsers install the security manager automatically. However, the security manager is not installed automatically in the case of running applications. While running the application, the java command line option -Djava.security.manager is provided. Consider the following code statement: java -Djava.security.manager MyFile
Therefore, you are applying the security policy to the MyFile application explicitly through this command statement. In the earlier versions of Java, applications were not run under security policies because all local code was trusted and assigned all system resources. Therefore, the -Djava.security.manager is a new flag in Java 2. You can also specify customized security managers for your Java applications. The following code statement shows the implementation of the previous concept. java -Djava.security.manager=NewSecurityManager MyFile
NOTE The Java applications can also call the setSecurityManager method of the java.lang.System class.
The security manager can refer to both the system-wide policy file or customized userdefined policy files for implementing security. I have already discussed the system-wide policy file, so let’s discuss the user-defined security policy in the next section.
JAVA SECURITY MODEL
Chapter 2
User-Defined Security Policy In addition to the default system policy file, you can create user-defined security policy files. To do this, you can use the -Djava.security.policy command. Consider the following code statements: java -Djava.security.manager -Djava.security.policy=NewPolicy MyFile java -Djava.security.manager -Djava.security.policy==NewPolicy MyFile
The first statement specifies that the NewPolicy file will be used in addition to the remaining security policy files for implementing application security, whereas the second statement specifies that only the NewPolicy file will be used for implementing application security and that all other files will be overridden.
Summary This chapter discussed the Java security model. First, I talked about the Java security features followed by a history of Java security models across various versions. The sandbox, the trusted code, and the Java 2 fine-grained security models were discussed in detail. Next, the Java 2 security tools and API were discussed. The class path in Java 2 is split into three separate paths, the system class path, application class path, and the extensions framework. All these were discussed in detail in the chapter. Then, the functionality of Java 2 class loader was discussed. Finally, I discussed the policy files and the security manager.
Check Your Understanding Multiple Choice Questions 1. The concept of signed remote code was introduced in which security model? a. The sandbox security model b. The trusted code security model c. The fine-grained security model d. The jdk1.1 security model 2. Which of the following statements is true? a. The permissions to access a particular system object are stored in the security policy file. b. You can have user-defined policy files.
53
54
Part I
INTRODUCTION TO JAVA SECURITY
c. JVM refers to multiple policy files while assigning permissions. d. The protection domain of a principal contains the system objects that are accessible by the specified principal. 3. Which of the following clauses are optional in a grant entry? a. SignedBy b. grant c. CodeBase d. Permission entries 4. Which of the following tools is used to add new libraries to the default class path? a. The CLASSPATH variable b. The –classpath flag c. The –cp flag d. keytool 5. What does the following code statement signify? java -Djava.security.manager -Djava.security.policy==NewPolicy MyFile
a. The NewPolicy file will be used in addition to the remaining security policy files for implementing application security. b. Only the NewPolicy file will be used for implementing application security and all other files will be overridden. c. No policy file is used for implementing application security. d. Only the security manager is used to implement application security.
Short Questions 1. Explain the extension mechanism in Java 2. 2. Explain the use of code source in grant entries. 3. Explain the working of the SecureClassLoader class.
Answers Multiple Choice Answers 1. b. The trusted code security model introduced the concept of signed remote code.
JAVA SECURITY MODEL
Chapter 2
2. a, b, and d. The permissions to access a particular system object are stored in the security policy file. This file can be edited either manually or by the Policy tool. The domain that contains system objects that can be accessed by a principal is called the protection domain. Principal is the entity to which permissions are granted, and it can access objects in the protection domain depending on the permissions specified for the particular object in the policy file. In addition to the default policy file, you can also have userdefined policy files. However, at a given point in time, the JVM can assign resources based on permissions specified in a single policy file. 3. a and c. The syntax of grant entries states that they begin with the grant keyword followed by the optional SignedBy or CodeBase clause and that they end with a list of permission entries. 4. a, b, and c. To add new libraries to the default class path, you can use the java tool, the CLASSPATH environment variables, and the –classpath and –cp flags. 5. b. Only the NewPolicy file will be used for implementing application security; all other files will be overridden.
Short Answers 1. The extensions mechanism is a new feature of Java 2. Extensions are groups of packages and classes used to extend the functionality of the Java platform. This is done by the extension mechanism where the runtime environment finds and loads extension classes without naming them in the class path. Extension classes are used in a similar manner as you use system classes. They are called so because they extend the core API of Java 2. There have been additions to the core API since the various version releases in Java. Therefore, the extensions mechanism extends the functionality of the Java platform without increasing the size of the core API. In Java 2, the process of adding new libraries is now considerably simple. You can skip the CLASSPATH environment variable or the -classpath command now. When you place the JAR file containing the extensions in the extensions directory, it is added. Such an extension is called an installed extension. On the other hand, a download extension is one in which you refer to a JAR file from another JAR file. Installed extensions are JAR files in the lib/ext directory. However the location of installed extensions can be changed during compile time by using the -extdirs flag followed by the directories separated by semicolons. Download extensions include classes and JAR files, which are specified in the Class-Path headers in manifests of other JAR files.
55
56
Part I
INTRODUCTION TO JAVA SECURITY
2. The code source consists of two parts, the code base and the digital certificates used to sign the class code. The code base specifies the URL or location from where the code is downloaded. The code base field of a grant entry is optional. When it is omitted, permissions can be granted to code from various sources. The following code statement specifies the code URL marked by the CodeBase keyword. CodeBase “http://www.mybooks.com”
The digital signatures for the code are specified by the SignedBy keyword. This field of a grant entry is again optional. When it is omitted, permissions can be granted to both signed and unsigned code. Consider the following code statements: SignedBy “Johnson” SignedBy “Johnson, Maria, Sam, Meredith”
The code must be signed by all the signers specified in the SignedBy clause list to be granted permissions. This statement stands true for multiple signers as well. 3. After executing a Java application or applet, an object of the SecureClassLoader class is created. This object locates and loads the class file of the specified Java program. Then, a subclass of the SecurityManager class is created at runtime. Finally, the main() or init() method is called. Therefore, the main task for implementing security in this process is imparted to the SecureClassLoader class, which ensures the safe loading of classes at runtime. The SecureClassLoader class locates classes for the JVM by following the class search path. It begins by looking for class files in the boot class path followed by the installed extensions. Finally, the classes specified by the application class path are checked. After the class is located and loaded in the JVM, the SecureClassLoader class specifies a protection domain for the class. First, a code source is created for the specified class by referring to the code base and the signatures attached to the code. Then, the code source fetches the protection domain of the class, which contains the specified permissions for access control of resources at runtime. Therefore, the SecureClassLoader class repeats this process until all classes required to run the program are loaded in the JVM along with their protection domains.
Chapter 3 The Java Virtual Machine
I
n Chapter 1, “Security: An Overview”, I discussed the development and execution environment in Java. You are aware that the execution environment consists of Java Virtual Machine ( JVM), which is chiefly responsible for the secure execution of Java programs. In this chapter, I’ll discuss the various components of JVM and their role in implementing security in Java programs.
JVM Safety Features JVM has several built-in security features that improve the robustness of Java programs. Most of these safety features are linked to the bytecode operating in JVM. A few of these safety features are structured memory access, which is devoid of any pointer arithmetic, array bounds checking, automatic garbage collection, and checking for null references. Due to these safety features, the programs that execute on JVM cannot exploit the memory as the memory can be accessed only in structured ways. In addition to memory checks, JVM checks how you refer to an object, access an array, or try to use a null reference. For any invalid task performed by you, JVM either raises a meaningful exception or terminates the running program. Therefore, the previous safety features not only make Java programs robust, they also help in making the execution of Java programs more secure. Unrestrained memory access is a major security risk. For example, if a hacker identifies the location of a class loader in memory, the hacker can reference the class loader by using pointers and manipulating the information stored in it. Because pointer arithmetic is not permissible in Java, the cracker’s harmful intentions of accessing JVM internal memory are restrained. Another feature that provides safety for memory access is that the memory layouts are not specified in the class files or JVM specification. Memory layouts refer to the data areas created at runtime in JVM. These data areas are memory locations that store the data needed by JVM for the execution of a Java program. For example, a method area is used to store bytecodes, and JVM heap is used to store the objects created by a running program. You will read more about these method areas in subsequent sections. Therefore, a class file does not contain any information regarding memory addresses. All this information is decided by JVM at runtime. For example, while executing a program, JVM decides where to store the loaded class files and the data contained within them in its internal memory. When the objects are created, JVM determines the memory address to be allocated to these objects. Therefore, a cracker cannot know
THE JAVA VIRTUAL MACHINE
Chapter 3
the memory addresses of the data contained in a class file by looking at it. The JVM specification also does not give them any clues regarding this information because the memory layout is not included in it. It is the task of JVM designers to decide the data structure and the memory location to represent the data areas in their JVM implementation. Another safety feature that contributes to the security of JVM is error handling through exceptions. Therefore, a JVM does not crash if it encounters a security violation; instead, it raises an exception that stops the execution of the thread in which the error was thrown. Let’s see how these safety features are built into the JVM architecture. The next section discusses this.
The JVM Architecture The behavior of a JVM implementation is defined in terms of subsystems, data types, and instructions. These components form the architecture of JVM. Every implementation of Java has a class loader subsystem and an execution engine. The class loader subsystem is responsible for loading classes and interfaces in JVM. The execution engine is responsible for executing the methods of the loaded classes. JVM needs memory to store objects, parameters to methods, return values, or the result of any computation. It organizes this memory in data areas such as heaps, method areas, and native method stacks. JVM components that implement security in the execution environment are the class loader, class file verifier, and security manager. The internal architecture of JVM is depicted in Figure 3-1. I’ll discuss the JVM components in detail in the subsequent sections.
The Class Loader When a program is executed, the data or files needed by the program are provided by the operating system. The operating system has access to the file system where it searches for class libraries. It also has access to all the I/O functions in the program being executed. However, in case of Java programs, this task is performed by the class loader. Prior to the execution of a Java program, JVM needs to fetch the respective classes associated with the program and load them in memory. The operating system is not involved in class fetching and loading because the classes are not loaded from the same location and therefore, an integrity check for classes cannot be defined. Classes are mainly of two types: trusted and untrusted. The JRE classes found in the
59
60
Part I
INTRODUCTION TO JAVA SECURITY
FIGURE 3-1 The internal architecture of JVM
boot class path are designated as trusted. The overhead of verification is removed for these classes and they can be directly executed unlike untrusted classes. In earlier versions of Java, all the local files were treated as trusted. However, in Java 2, all local classes that are not specified in the boot class path are treated as untrusted. The classes loaded from a network or a Web server are also treated as untrusted. The class verifier verifies all the untrusted classes, and these classes are assigned permissions as specified in the security policy of the security manager. Therefore, due to the different class sources, the mechanism for loading them is also different. JVM may support more than one class loaders, which are supported by the class loader subsystem. A Java application can support two types of class loaders, the primordial class loader and the class loader objects. There is a single primordial class loader, which is a part of JVM implementation. The primordial class loader loads only the trusted classes and Java API classes. On the other hand, the class loader objects are programmed to load classes in customized ways. For example, they may be used to load classes from the network or a remote Web server. Therefore, JVM trusts the classes loaded by the primordial class loader and does not trust those loaded by the class loader objects. Thus, the classes loaded by the primordial class loader need not be verified by
THE JAVA VIRTUAL MACHINE
Chapter 3
FIGURE 3-2 The class loader architecture
the class file verifier and may be directly passed to the execution engine. However, this does not hold true for the classes loaded by the class loader objects, which need to be verified by the class file verifier. Figure 3-2 depicts this class loader architecture.
The Class File Verifier The class file verifier checks that the loaded class files have a proper internal structure. If the specified class file is not up to the mark, the class file verifier raises an exception. Class files can also be generated without the Java compiler. Therefore, class files can be created by hackers to solve their malicious purposes. Because class files store just binary information, it is difficult for the class file verifier to distinguish between the class files generated by the compiler and those generated by other means. A class file verifier helps in implementing security by achieving program robustness. For example, imagine that a cracker places a class file containing a method that loops beyond the limits specified for an array. Such a method could either cause JVM to crash or temporarily suspend its operation. It is the task of the class file verifier to check for class file integrity and avoid such incidents. The verifier checks the size, structure, and features of a class file. It is up to the JVM designers to decide when
61
62
Part I
INTRODUCTION TO JAVA SECURITY
JVM will perform these checks. However, most of them design JVM such that these checks are performed just after the classes are loaded in JVM. You’ll read more about how the class file verifier actually achieves program robustness in Chapter 5, “The Class Loader and the Class File Verifier”.
The JVM Heap The heap stores all the objects being created by a running Java program. Objects are created dynamically by using the new operator. The memory for these objects is allocated in the heap at runtime. Garbage collection refers to the process of releasing the memory held by these objects once they are destroyed or are no longer needed in the program. This implies that the memory held by an object that is no longer needed in the program is recycled. Instead, this heap space can be used by another object. The garbage collector is also involved in heap fragmentation. When a program is still executing, the objects that are being cleared by the garbage collector leave free blocks of heap memory between the memory spaces already occupied by active objects. Assume that an object that needs to be allocated requires less memory space than the total free memory in the heap. However, this memory space is not present in continuous memory locations, indicating a need to increase the memory size of the heap. This can affect the performance of a currently running program. Java does not contain a default implementation for a garbage-collector heap. The JVM specification only includes the need for a heap and a garbage collector. However, JVM does not specify the working of the heap or the garbage collector. Therefore, it is up to the JVM designer to decide on the implementation of the heap and the garbage collector. This acts as a good security measure because a hacker has no idea about how JVM stores objects in the heap. This renders it difficult to attack JVM through memory definitions.
The Method Area When you define a new class or an interface in your Java program, you actually provide a new type for the program. This new type can be used as other data types for creating instances in the Java program. When types are loaded by JVM, all the typespecific information such as the class attributes and the class methods are stored in a memory area called the method area. The class information stored in the method area is similar to the information stored in the class file. The only difference is that the organization of information in the method area is not as per Java specifications. Therefore, JVM designers actually decide how to store information in the method area. The designers should keep in mind two options while designing the method area implementation, namely, the execution speed of the application and the memory con-
THE JAVA VIRTUAL MACHINE
Chapter 3
straints. Therefore, designers should choose such data structures that facilitate speedy execution of Java programs. However, they might have to trade off the memory aspect in this case because redundant information is stored in the method area to increase execution speed. It is up to the designer to decide the data structures, which enhance the performance of their implementation. The size of the method area is not fixed and can be increased or decreased according to the application requirement. The method area need not assign contiguous locations of memory; the memory can be assigned in a heap. Therefore, the concept of garbage collection also applies to the method area.
The Native Method Stacks In the preceding sections, you have already read that data areas are defined by JVM at runtime. However, a currently executing Java application may also need data areas for the native methods. There are very few security restrictions for native methods. Thus their capabilities are not limited. Native methods can use runtime data areas such as registers, stacks, or heaps for memory allocation. They are implementation-dependent, and the designers can decide how a native method will be invoked in a running Java application. When a running program invokes a native method, JVM directly links to the method instead of pushing the necessary information on to the stack. A native method interface uses a native method stack for locating and loading native methods in JVM. For example, assume that the native method interface uses the C language. Then, the native method stacks would be C stacks. The native method stacks in this implementation can contain function parameters and return values. Therefore, the implementation of the native method interface and the native method stack is entirely designer-dependent.
Just-in-Time (JIT) Compilers You are already familiar with the fact that the bytecodes generated after compilation are interpreted at runtime in the execution engine. This involves the conversion of each bytecode instruction to its native counterpart. This increases the execution time for Java programs, thereby affecting their performance. Therefore, to improve the performance of Java programs, the number of times the bytecode instruction is converted to machine code by JVM interpreter needs to be reduced. Note that the security aspect of the Java programs is attributed to the bytecode and the class file format. These collectively allow you to run your Java programs on any JVM and also test the code for safety. The bytecode should be translated to native machine code only after the class files are loaded and verified. Either the class files, as a whole, can be translated into machine code after being loaded and verified, or only the methods required
63
64
Part I
INTRODUCTION TO JAVA SECURITY
currently can be converted to machine code. If class file, as a whole, were translated to machine code, it would reduce the performance of the Java program. This is so because some methods in the class file may not be executed and the time taken for their conversion to native machine code would be wasted. Therefore, the second option of translating only the methods that need to be executed sounds better as it would improve the program performance. This process of translating bytecode to machine code as and when required is called JIT compilation. JIT compilers are very effective because they compile only the code that is actually executed. Therefore, the methods that are executed frequently can be retrieved from the native method area and executed on the fly. This process occurs during runtime.
The JVM Execution Engine The core component of a JVM implementation is the execution engine, which is responsible for the invocation and execution of methods. In JVM specification, the functionality of the execution engine is defined in terms of the instructions it executes. Once the specified class files have been loaded, the execution engine invokes the main function of the initial class in the program. After this, the execution of the program depends on the type of instruction loaded currently. The different types of instructions are for: ◆ Object creation ◆ Arithmetic calculation ◆ Array manipulation ◆ Type conversion ◆ Control flow modification
Therefore, through these types of instructions, the execution engine looks after memory management and thread management, and also invokes native methods.
The Security Manager When code is loaded from different sources, it is checked for integrity by the class file verifier. This check is, however, performed for the internal class loaders within JVM. For external checks from JVM, you can use the security manager. For example, you can use the security manager to prevent applets from accessing your file system or prevent applications from starting unsafe network connections. Security managers can be customized; therefore, you can create custom policy files for your Java applications. The permissions for the unsafe tasks performed in an application are defined in this custom security policy. For each such action, there is a corresponding method defined
THE JAVA VIRTUAL MACHINE
Chapter 3
in the security manager, which checks whether the specified action is permissible. Each such method begins with the check keyword. For example, a checkWrite() method checks whether a specified application thread is allowed to write in a given file on the system. The following actions are regulated by the check methods in the security manager. ◆ Writing to a file ◆ Reading from a file ◆ Deleting a file ◆ Opening a connection with a specified host ◆ Accepting a connection from a specified host ◆ Causing a specified application to exit
There are many more actions in addition to those listed here that are regulated through check methods in the security manager. You’ll read more about security managers in Chapter 6, “Java 2 Security Manager”.
Summary In this chapter, I discussed the security and safety features of JVM. I also discussed the components and the architecture of JVM that contribute to security. The JVM components were then discussed in detail. I discussed the use of JIT compilers for improving the performance of Java programs. The class loader, class file verifier, and security manager are the core components of JVM that contribute to the security of the system. I discussed these components and the runtime data areas namely, the JVM heap, the method areas, and the native method stacks.
Check Your Understanding Multiple Choice Questions 1. Which of the following are JVM safety features? a. Structured memory access b. Automatic garbage collection c. Pointer arithmetic d. Array bounds checking
65
66
Part I
INTRODUCTION TO JAVA SECURITY
2. Which of the following statements is true? a. JVM decides where to store the loaded class files. b. JVM determines the memory address to be allocated to new objects. c. Memory layout information is included in the class files. d. JVM designers decide the data structure and the memory location to represent the data areas in their JVM implementation. 3. Which of the following are JVM components? a. Class loader b. Class file verifier c. Native method stacks d. keytool 4. Which of the following statements is true for class loaders? a. JVM can support multiple class loaders. b. JVM supports multiple primordial class loaders. c. JVM trusts the classes loaded by the primordial class loader. d. Classes loaded by class loader objects are not verified by the class file verifier. 5. Which of the following tasks is performed by the class file verifier? a. The class file verifier checks that the loaded class files have a proper internal structure. b. The class file verifier creates class files. c. The class file verifier checks for class file integrity. d. The class file verifier checks the size, structure, and features of a class file.
Short Questions 1. Explain just-in-time compilation. 2. What is the need for native method stacks? 3. Explain garbage collection and its need.
Answers Multiple Choice Answers 1. a,b, and d. The JVM safety features are structured memory access, which is devoid of any pointer arithmetic, array bounds checking, automatic garbage collection, and checking for null references.
THE JAVA VIRTUAL MACHINE
Chapter 3
2. a, b, and d. When the objects are created, JVM determines the memory address to be allocated to these objects. Therefore, a cracker cannot know the memory addresses of the data contained in a class file by looking at it. The JVM specification also does not give them any clues regarding this information because the memory layout is not included in it. 3. a,b, and c. JVM organizes memory in data areas such as heaps, method areas, and native method stacks. JVM components that implement security in the execution environment are the class loader, class file verifier, and security manager. 4. a and c. JVM may support more than one class loader, which are supported by the class loader subsystem. There is a single primordial class loader, which is a part of JVM implementation. The primordial class loader loads only the trusted classes and Java API classes. On the other hand, the class loader objects are programmed to load classes in customized ways. Therefore, JVM trusts the classes loaded by the primordial class loader and does not trust those loaded by the class loader objects. Thus, the classes loaded by the primordial class loader need not be verified by the class file verifier and may be directly passed to the execution engine. However, this does not hold true for the classes loaded by the class loader objects, which need to be verified by the class file verifier. 5. a, c, and d. The class file verifier checks that the loaded class files have a proper internal structure. It is the task of the class file verifier to check for class file integrity. The verifier checks the size, structure, and features of a class file.
Short Answers 1. The bytecodes generated after compilation are interpreted at runtime in the execution engine. This involves the conversion of each bytecode instruction to its native counterpart. This increases the execution time for Java programs, thereby affecting their performance. Therefore, to improve the performance of Java programs, the number of times the bytecode instruction is converted to machine code by JVM interpreter needs to be reduced. Note that the security aspect of the Java programs is attributed to the bytecode and the class file format. These collectively allow you to run your Java programs on any JVM and also test the code for safety. The bytecode should be translated to native machine code only after the class files are loaded and verified. Either the class files, as a whole, can be translated into machine code after being loaded and verified, or only the methods required currently can be converted to machine code. If class file, as a whole, were translated to
67
68
Part I
INTRODUCTION TO JAVA SECURITY
machine code, it would reduce the performance of the Java program. This is so because some methods in the class file may not be executed and the time taken for their conversion to native machine code would be wasted. Therefore, the second option of translating only the methods that need to be executed sounds better as it would improve the program performance. This process of translating bytecode to machine code as and when required is called JIT compilation. JIT compilers are very effective because they compile only the code that is actually executed. Therefore, the methods that are executed frequently can be retrieved from the native method area and executed on the fly. This process occurs during runtime. 2. A currently executing Java application also needs data areas for the native methods. There are very few security restrictions for native methods. Thus their capabilities are not limited. Native methods can use runtime data areas such as registers, stacks, or heaps for memory allocation. They are implementation-dependent, and the designers can decide how a native method will be invoked in a running Java application. When a running program invokes a native method, JVM directly links to the method instead of pushing the necessary information on to the stack. A native method interface uses a native method stack for locating and loading native methods in JVM. For example, assume that the native method interface uses the C language. Then, the native method stacks would be C stacks. The native method stacks in this implementation can contain function parameters and return values. Therefore, the implementation of the native method interface and the native method stack is entirely designer-dependent. 3. Garbage collection refers to the process of releasing the memory held by objects once they are destroyed or are no longer needed in the program. This implies that the memory held by an object that is no longer needed in the program is recycled. Instead, this heap space can be used by another object. The garbage collector is also involved in heap fragmentation. When a program is still executing, the objects that are being cleared by the garbage collector leave free blocks of heap memory between the memory spaces already occupied by active objects. Assume that an object that needs to be allocated requires less memory space than the total free memory in the heap. However, this memory space is not present in continuous memory locations, indicating a need to increase the memory size of the heap. This can affect the performance of a currently running program.
Chapter 4 Class Files
Y
ou are already clear about the fact that the various popular features of Java such as platform-independence and security can be attributed to the Java bytecode rather than to the Java source code. In the preceding chapters, you have learned that the compilation of the Java source code results in the generation of bytecode. In this chapter, I’ll discuss mainly the development and format of class files, which are generated in the last stage of the development process. First, let’s discuss the compilation process in Java, which results in the creation of class files.
The Compilation Process During compilation, a Java source program that is written in a high-level language is converted to bytecode, which is in a machine-level or low-level language. A Java source program may contain multiple class definitions. During compilation, all these class definitions are stored in a single class file. Now, this Java source program may even contain method calls to different classes that are defined elsewhere. Therefore, the class file generated contains references to these classes in addition to the machine code derived after compilation. The generation of the class file is the last step in the development environment. You might be wondering whether there is a separate linking phase to solve the inter-class references. No, there is no separate linking phase, and the JVM loads the referenced files as and when needed during runtime. However, this dynamic linking process requires that the class file contain the names of the referenced classes along with the name of the methods and fields of the classes that are being used in the Java source program. For a particular Java source code, you need not have only one bytecode after compilation. This means that you can generate bytecode without the Java source code also. Every program written in Java is compiled into bytecode; however, you can also create class files or produce bytecode without using the Java compiler. This bytecode generated by external methods can also pass through the integrity check performed by the class verifier. Therefore, these deviant class files can be easily created and run in the JRE. You must have guessed by now the security threats caused by this. This inconsistency between the Java source code and its respective bytecode is an obvious loophole in the basic security implementation of Java. Let’s understand what goes into a class file that makes it susceptible to hacker attacks. The next section discusses the class file format.
CLASS FILES
Chapter 4
The Class File Format in Java 2 Each class file in Java contains at least a Java class or an interface. The class file format is presented in the form of pseudo-structures that can be compared to the structures in C. The contents of these structures are referred to as items. The class file data is marked by u1, u2, and u4 types. These types indicate unsigned one-, two-, and fourbyte quantities. A class file contains a single ClassFile structure, which is given here: ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }
Let’s discuss the various items of the ClassFile structure. ◆ The magic item represents the magic number that identifies a class file. It has the default value, 0xCAFEBABE. ◆ The minor_version and major_version items represent the minor and
major version numbers of the compiler that created the specific class file. For example, assume that the JVM implementation supports a particular major version number and the code to be run has a different major version. This difference in major version numbers indicates a high incompatibility issue that requires a different implementation of the JVM. ◆ The constant_pool_count item represents the number of entries in the constant_pool table of the class file. An entry in this table is marked by the constant_pool index. The valid entries of this index are between zero
71
72
Part I
INTRODUCTION TO JAVA SECURITY
and the constant_pool_count value. The entry at index zero is included in the count. However, it is not included in the constant_pool table of the class file. ◆ The constant_pool table is an array containing heterogeneous structures such as the class names, field names, and constants specified in the ClassFile structure. The first entry in the class file is marked as constant_pool [1] because the entry at index zero is used for internal purposes by the
JVM. You will read more about the constant pool in the subsequent section. ◆ The value of the access_flags item represents the modifiers for the class and interface declarations. For example, the ACC_PUBLIC access flag is used
by classes and interfaces to represent that they are public and can be used by elements that are external to their package. Alternatively, the ACC_FINAL access flag cannot be set for interfaces because their implementation would otherwise never be complete. Therefore, this access flag is set only for the classes that do not want any subclasses to be derived from them. ◆ The this_class item represents a class or interface defined in the specified class file. The value of this item is a valid element of the constant_pool
table. ◆ The super_class item represents the superclass of the class defined in the
specific class file. The value of this item may be zero or a valid entry in the constant_pool table. If the value is zero, it indicates that the specified class
in the class file has no superclass; otherwise the value of this item represents a constant_pool entry specifying the superclass of the class in the class file. Keep in mind the fact that a superclass cannot be final. ◆ The interfaces_count item gives the total number of super interfaces for
the specific class or interface. ◆ The interfaces[] item is an array of the interface implemented by the
specified class in the class file. Each value of the array should be a valid index in the constant_pool table. ◆ The fields_count item represents the number of field_info structures in the fields table. These structures represent the fields contained in the class
and the instance variables for the specified class in the class file. ◆ The fields[] array item contains the field_info structures for all the
fields specified in the class or interface definition. It does not specify the fields that are inherited from superclasses or interfaces. ◆ The methods_count item represents the number of method_info structures in the methods table. These structures represent the methods contained in the class. They also specify the init() method of the instance variables for
the specified class in the class file.
CLASS FILES
Chapter 4
◆ The methods[] array item contains method_info structures for the methods
specified in the class. These structures also represent the JVM code for the class methods. ◆ The attributes_count item specifies the number of attributes in the attributes table of the class. ◆ The attributes[] array item contains all the attributes contained in the
class file. The JVM can ignore the attributes that it does not recognize. The only attribute defined for the attributes table is the SourceFile attribute. This attribute specifies the name of the source file from which the class was created. There can be only one SourceFile attribute in the attributes table. In addition to the previous elements, the class file also contains tables for interfaces, fields, methods, and attributes. A 2-byte number specifying the number of entries contained in the table precedes each table. Each entry in a particular table has a structure type specified by the table. The methods table is the most important of them all because it contains the bytecode that is executed on the JVM. The methods table contains entries that are structures of the type, method_info. The code for this structure follows: method_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; }
I discussed previously that the constant_pool table contains heterogeneous structures within it. Let’s discuss the contents of the constant pool in detail.
The Constant Pool The constant pool acts as a symbol table for linking during runtime and a data store for the constants and literal values contained in the source code. It is an array that is referenced through index numbers from the other sections of the class file, such as the field and method sections. All the entries in the constant table have the following basic format. cp_info { u1 tag; u1 info[]; }
73
74
Part I
INTRODUCTION TO JAVA SECURITY
Each entry in the table begins with a 1-byte tag that describes the kind of information stored in the entry. Table 4-1 describes the types and values of the entries stored in the constant pool. Table 4-1 Types and Values of the Constant Pool Entries Type
Value
Class
7
Fieldref
9
Methodref
10
InterfaceMethodref
11
String
8
Integer
3
Float
4
Long
5
Double
6
NameAndType
12 1
Utf8
Let’s discuss each of these types in detail.
Class The Class structure is used to represent a class or interface and can be described by the following code: CONSTANT_Class_info { u1 tag; u2 index; }
The tag item of the previous structure takes the value of the Class structure; 7. The value of the index entry is a valid index of the constant_pool table.
Fieldref, Methodref, and InterfaceMethodref The Fieldref, Methodref, and InterfaceMethodref constant types are described by the following code structures. These types are a symbolic reference to a field, a method, and an interface method, respectively.
CLASS FILES
Chapter 4
CONSTANT_Fieldref_info { u1 tag; u2 class_index; u2 name_and_type_index; } CONSTANT_Methodref_info { u1 tag; u2 class_index; u2 name_and_type_index; } CONSTANT_InterfaceMethodref_info { u1 tag; u2 class_index; u2 name_and_type_index; }
The tag item in these code structures takes the value described in Table 4-1. The value of the class_index item is a valid index of the constant_pool table. The entry at the specified index is a CONSTANT_Class_info structure describing the class or interface containing the specified field or method. Therefore, the class_index item of the Fieldref or Methodref structure must be a class type, whereas the class_index item of the InterfaceMethodref structure must be an interface type. The name_and_type_index entry represents the name and descriptor of the specified field or method.
String The String type is used to represent constants of the java.lang.String type. This type is represented by the following structure: CONSTANT_String_info { u1 tag; u2 string_index; }
The tag item of this structure takes the value of the String type— 8. The string_index item represents the characters to which the java.lang.String object is initialized.
75
76
Part I
INTRODUCTION TO JAVA SECURITY
Integer and Float The Integer and Float items represent 4-byte numeric constants of the int and float types. These items are represented by the following code structures. CONSTANT_Integer_info { u1 tag; u4 bytes; } CONSTANT_Float_info { u1 tag; u4 bytes; }
The tag items of the Integer and Float structures take the values of 3 and 4, respectively. The bytes items of both these structures contain the value contained in the int and float constants.
Long and Double The Long and Double types represent the 8-byte numeric long and double constants. They are represented by the following code structures. CONSTANT_Long_info { u1 tag; u4 high_bytes; u4 low_bytes; } CONSTANT_Double_info { u1 tag; u4 high_bytes; u4 low_bytes; }
The tag items of the Long and Double structures contain the values 5 and 6, respectively. The high_bytes and low_bytes items of both these structures collectively represent the value contained in either the double or long constant.
CLASS FILES
Chapter 4
NameAndType The NameAndType structure can be used to represent fields and methods without indicating the name of the class or interface to which they belong. The code for this structure follows: CONSTANT_NameAndType_Info { u1 tag; u2 name_index; u2 descriptor_index; }
The tag item defined in this structure contains the value 12. The name_index item represents the names of the fields and methods that act as Java identifiers. The value contained in this item is not a fully qualified name; however, it is used to recognize the concerned fields and methods in a program. The descriptor_index item represents a valid Java descriptor for fields and methods.
Utf8 This constant type is used to represent constant string values in the Utf8 format. In the Utf8 format, the strings containing non-null ASCII characters are represented by using only one byte per character. The strings represented in this format are not nullterminated. However, characters of up to 16 bits can be represented. This type is represented by the following code structure: CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; }
The tag item contains the value 1. The length item represents the number of bytes in the bytes array. The bytes array has the number of bytes contained in the string. Therefore, looking at the description of the previous items, I can summarize that a class file contains information regarding the classes contained within it. It also contains information regarding the JVM implementation for the specified class file. Further, the class file contains specific information, such as the fields, attributes, methods, interfaces, or subclasses for the classes defined within it.
77
78
Part I
INTRODUCTION TO JAVA SECURITY
However, all the previous information contained in a class file poses a risk to security, because hackers can use it to tamper with your system. The next section discusses the security threats posed due to class files.
Decompilation Threats All the detailed information available in a class file enables a hacker to derive the Java source code, although this process is not very easy. The tool needed for this purpose is a decompiler that converts Java bytecode to the original source code. In other words, it undoes the work done by the compiler. The decompilation process is also referred to as reverse engineering. You can use the Mocha decompiler to decompile class files into source code. Compilers generally render it ineffective for converting machinelevel language to high-level language because they remove a lot of additional information such as the variable and function names. This information is needed by decompilers to perform their tasks. In case of the java compiler, the only information lost during compilation are the comments, which cannot be retrieved by the Mocha decompiler. However, the variable and method names are self-explanatory and their functionality can be understood even without comments. Although the code generated by the Mocha decompiler is not completely similar to the Java source code, it is easily understandable and can be modified. Let’s take an example to understand the decompiling process by the Mocha decompiler. Consider the following code: import java.io.*; public class test{ public static void main(String args[]) { System.out.println(“Testing the MOCHA Decompiler”); } };
Compile this code by executing the following statement: javac test.java
On execution, this command generates the test.class file, which I’ll decompile using the Mocha decompiler. You can run the decompiler by using the following code statement: java mocha.Decompiler test.class
This code statement generates the test.mocha file and places it in the same directory as the class file. Figure 4-1 displays the test.mocha file as seen in Notepad.
CLASS FILES
Chapter 4
FIGURE 4-1 The test.mocha file in Notepad
Even though the code in the test.mocha file is not exactly similar to the source code, it is easily understandable. Therefore, hackers or even Java developers can use this decompiler to leak out business secrets or decompile a competitor’s project, respectively. However, a hacker need not go into this tedious task of decompiling class files, changing the source code, and then again compiling them to obtain the corrupted class files. A person who has substantial knowledge of Java and class file format can easily update, insert, or delete code in the class files. These changes are done smartly so that the class file verifier does not identify them.
Benefits of Decompilation The decompiler is not just used by hackers for malicious purposes. You can, in turn, use it for several important reasons. For example, imagine that you delete the source code for a Java program by mistake. However, you still have the compiled class file for the particular source. Then, you can use the decompiler to recover the lost Java source code. There are more reasons for you to use the decompiler, some of which are specified here. You can refer to the source code of your development partner who is creating a Java project with you. Decompilers can also be used to check the validity of the Java class files that you receive from an unknown source. It is morally and legally acceptable to use a decompiler on your own programs. However, it is not so if you use it to decompile someone else’s programs or steal the source code of copyrighted programs.
79
80
Part I
INTRODUCTION TO JAVA SECURITY
You might be wondering how to protect your Java code from the aforementioned decompiler threats. The next section discusses just that.
Preventing Decompilation Threats One method to restrict reverse engineering of bytecode is to control physical access to the program. Users accessing the program may need to interact with an interface with minimal number of services. However, this client-server model may affect program performance due to network limitations. Decompilers look for recognizers in the Java class file, using which they can easily translate the bytecode to its corresponding source code. As you have read in the preceding sections, such information is not hard to decipher, looking at the amount of detailed class file information contained in the constant pool. You have seen that information such as class, field, and method references are very readily available from the constant pool. Hackers can easily use this information for malicious purposes. You cannot completely erase the threat of decompilation; however, it can be reduced to a large extent. I’ll discuss the various protection techniques against decompilation in the subsequent sections.
Code Obfuscation You can make the source code difficult to read so that no one can decipher it easily. This technique is known as code obfuscation, which refers to confusing the reader by hiding important details. This is generally done by removing comments from the code. This is what Mocha does too. However, the reader can recognize the functionality of the various methods and fields by reading their meaningful names. You need not worry as you can also replace variable and method names by meaningless characters using the code-obscuring techniques. Therefore, this technique retains the compatibility of the source code in the Java environment, although the source code is not easily decipherable by human readers. This does not, however, indicate that your code is protected from all human readers. There might be some smart hackers who might still succeed in deciphering this obscured code. This risk always remains. To protect Java bytecode from reverse engineering, you can apply a bytecode obfuscator on the specified class files. Crema is one such example of a Java obfuscator. The same author who released Mocha, the decompiler, released Crema. Crema scrambles the symbolic reference information available in class files, such as the references to the field or methods of a specified class. This makes a class file less vulnerable to decompilation. For example, Crema scrambles class, interface, superclass, fields, and method names. You might be wondering how the JVM will link to the library packages if these symbolic references are missing in a class file. Do not worry, as Crema scrambles the references but rearranges them in such a way that the JVM still functions
CLASS FILES
Chapter 4
correctly. Now, if this code were decompiled using Mocha, it would raise exceptions, as it would be confused by the jumbled symbolic references, thus rendering reverse engineering difficult. However, as discussed earlier, this cannot stop determined hackers from achieving their evil goals.
Bytecode Hosing Another software that helps in suppressing reverse engineering is HoseMocha. HoseMocha is a bytecode hoser utility that appends extra instructions after a return instruction in the source code. For example, it splits the bytecode into different instruction sequences such as a PUSH operation followed by a POP operation. However, hackers can also suppress this technique. When the bytecode that has been hosed is decompiled, it results in readable code but it is spread with misleading passages. It is more readable than code after obfuscation. There is another serious implication in the use of bytecode hosers such as HoseMocha. They affect the performance of JIT compilers. You already know that JIT compilers are used to improve the performance of Java programs by reducing the interpretation overhead. JIT compilers read patterns in the bytecode, which can be converted to native code. Now, if you rearrange these patterns by using a bytecode hoser, you make the task of the JIT compiler difficult.
Salting Salting code refers to the process of inserting non-functional code or eccentric symbols to the Java source code. Therefore, salting makes a code difficult to decipher. An example of code insertions can be intentional loops inserted by the programmer. These code insertions compile correctly in the program; however, they do not affect the functionality of the program. Salting can cause unforeseen bugs to creep into your programs thereby affecting its overall performance. It can also be used as a technique to guard code from copyright issues. Moreover, these techniques do not completely safeguard you from decompilation techniques. Therefore, it is the task of developers to ensure that they do not transmit sensitive data such as passwords or private cryptographic keys in a class file. This is a serious risk in the client-server architecture where data is transferred from a server to a client and vice versa. Java 2 provides tools to store classes in JAR files, which are then transmitted across the network. Client-server applications need a much more secure server-side where sensitive data can be stored. All these tools do not completely suppress the risk of attacks to sensitive data. However, they make the task of reverse engineering less feasible in terms of time and money. The most you can do is to store the sensitive data under legal and copyright laws.
81
82
Part I
INTRODUCTION TO JAVA SECURITY
You have read so much about bytecode that you must be keen to explore the intricacies of bytecode instructions. The next section discusses the Java bytecode in detail.
Java Bytecode: Basics You must already be clear about the fact that the Java source code, when compiled, is not converted directly to machine code. Instead, it is converted to an intermediate bytecode format, which is, in turn, executed by the JVM. There may be a few Java developers who might have seen this intermediate bytecode due to the limitations of the tools being used by them. However, the Java SDK provides you with an assembler, javap, which converts the bytecode to a human-readable format. This is a command-line tool that examines the constant pool and other contents of the class file and produces a readable version of the bytecode contained in the class file. Let’s observe the functionality of the javap tool by reviewing the following Java source code. public class javapTrial { public static void main (String[] args) { System.out.println(“This is a trail of the javap command”); } }
After the class is compiled, the generated .class file can be disassembled by using the javap command utility. This converts the class file into mnemonics that can be interpreted by human readers. You will use the following command to perform the disassembling process. javap -c javapTrial
Figure 4-2 displays the output of this command. Let’s try to understand the output displayed in Figure 4-2. I’ll first consider the main method, as follows: Method void main( java.lang.string[]) 0 getstatic #2 3 ldc #3 <String “This is a trial of the javap command”> 5 invokevirtual #4 <Method void println(java.lang.String)> 8 return
CLASS FILES
Chapter 4
FIGURE 4-2 Output of the javap command
The initial integer before the instructions in the methods represents an offset of the instruction in the method definition. Therefore, the first instruction starts with 0. The offset is followed by the mnemonic of the instruction. The first instruction, getstatic, pushes a static field on to a data structure called an operand stack. The instructions following the specified instruction can pick the field from the data structure and use it in their computations. After the getstatic instruction, you can see the reference number, #2, which indicates the field to be pushed to the stack. In this case, the field to be pushed is java.io.PrintStream out. However, this field information is not directly visible in the bytecode. Instead the bytecode contains a reference to the particular field. This field information is stored in the constant pool, thereby reducing the size of the bytecode instructions. Therefore, the number#2 indicates that field information is stored at location #2 in the constant pool. Now, as you have analyzed the first statement, the interpretation of the remaining statements becomes comparatively easy. The next instruction follows: 3 ldc #3 <String “This is a trial of the javap command”>
The ldc instruction is also referred to as the load constant instruction. It pushes the string constant, “This is a trial of the javap command”, to the operand stack. Finally, the invokevirtual instruction invokes the println method. This method requires two arguments, the string argument and the this reference, which are, in turn, popped from the operand stack. The bytecode instructions can be divided into few basic categories: ◆ Pushing constants onto the operand stack ◆ Accessing arrays ◆ Stack manipulation through PUSH and POP instructions
83
84
Part I
INTRODUCTION TO JAVA SECURITY
◆ Arithmetic and logical instructions ◆ Control transfer or return ◆ Invoking methods ◆ Creating objects
You must have noticed that even at the bytecode level there are references to the fields and methods available in the source program. This indicates that Java bytecode is also object-oriented. You might be wondering about the differences in Java bytecode and assembly language code. Even though the functionality of the two is almost similar, there are certain design differences in them. The JVM works under a stack architecture, where values are pushed into or popped from a stack. Alternatively, the assembly language processor has a register-based architecture, where the values are stored into temporary memory locations called registers. Comparing the two architectures, the stack-based architecture is easier to implement and port to different platforms. Moreover, understanding the functionality of the code is much easier in the stackbased architecture. As already discussed, the Java bytecode is object-oriented, making it easier for the JVM to check for method or field type discrepancies during runtime. Alternatively, in assembly language processors, the object-oriented features of the high-level language are lost when the program is compiled. This fact leads to security loopholes because the runtime engine cannot check for validity issues in field and method references. Therefore, type safety is an important feature for implementing security.
Type Safety Java programs look very similar to C++ programs; however, Java programs implement security whereas C++ programs don’t. This is due to the prevention of memory bugs by the Java bytecode. Memory bugs are a very important aspect towards attacking an otherwise safe code. For example, consider a very simple Java program that adds a float and an integer number. public class addNumber { public float add(float c, int d) { return c+d; } public static void main (String[] args) { int a = 10; float b = 2;
CLASS FILES
Chapter 4
addNumber num=new addNumber(); System.out.println(“The sum of the numbers is”+ num.add(b,a)); } }
When this program is compiled and disassembled by using the javap command, the output bytecode is as shown in Figure 4-3. Let’s consider the bytecode for the add method. It is listed here. Method float add(float, int) 0 fload_1 1 iload_2 2 I2f 3 fadd 4 freturn
Before executing a Java method, the JVM stores the method parameters in a data structure. Therefore, if we consider this method, there would be three entries in the respective data structure, namely, the this reference, the float parameter, and the integer parameter. To further edit these parameters, they need to be placed on the operand stack.
FIGURE 4-3 The bytecode for the addNumber.class file
85
86
Part I
INTRODUCTION TO JAVA SECURITY
Therefore, the instructions, fload_1 and iload_2, load the float and integer parameters to positions 1 and 2 of the operand stack. The noticeable fact about these entries is that they start with an f and i prefix, respectively. This indicates that the bytecode instructions are strongly typed, which is another very important security feature of the JVM. Refer to Table 4-2 for the various types of prefixes allowed in the Java bytecode. Table 4-2 Java Bytecode Type Prefixes Type
Prefix
Integer
i
Float
f
Double
d
Byte
b
Long
l
Short
s
Character
c
Object Reference
a
During runtime, if the type of a parameter does not match its respective entry in the bytecode, the JVM rejects the bytecode as being unsafe. These type-safety checks are performed only during the class load time. Tampering with these type safety checks would lead to serious security implications. For example, if hackers succeed in blindfolding the JVM so that the JVM treats an integer as a float or vice versa, they could perform malevolent calculations such as tampering with an online bank application. Memory checks need to be implemented in case of array manipulation techniques. Bytecode instructions such as aaload and aastore operate on arrays and check the array memory bounds as well. If someone tries to exceed the memory bounds of an array, these instructions throw ArrayIndexOutOfBoundsException. Another area susceptible to attacks is branching instructions. However, there are certain limitations placed on branching instructions in Java bytecode, as follows: ◆ Branching instructions can only branch to instructions in the same method. ◆ Transferring instructions outside a given method is possible only with the return instruction or through the invoke instruction.
The point highlighted in the previous sections is that Java differs from the remaining object-oriented languages in the fact that it controls security on the compiled code, whereas the remaining languages, such as C++, avoid memory errors at the source code level.
CLASS FILES
Chapter 4
Considering the type safety of bytecode, you cannot conclude that this safety is available for all the viable Java bytecodes because not all bytecodes are typed. There is minimal type support in the JVM for byte and short values. Even these data types are not well supported in the constant pool. Moreover, the JVM processor stack is limited to 32 bits. Therefore, the JVM manages values longer or shorter than this by special instructions. Examples of such values are long, double, short, and byte. If the Java source code is being compiled to bytecode, the compiler takes care of these special instructions. However, you are already clear about the fact that bytecode can also be created without the Java compiler. In such cases, the permissible limits that these data types can take are not controlled. Due to the lack of one-to-one correspondence between the Java source code and its corresponding bytecode, a lot of work needs to go into verifying the bytecode being executed by the JVM. This is because the Java class verifier passes those class files as well that have not originated from pure Java source code. These class files may contain code not derived from a trusted source. Therefore, further checks need to integrated with the class file verifier to avoid such major security flaws in Java. The ease with which the deviant class files pass the class file verifier needs to be controlled.
Summary In this chapter, I discussed the compilation process in Java that results in the creation of class files, which is the major focus area in this chapter. Next, I discussed the class file format in Java 2. Then, I discussed the threats faced due to reverse engineering or decompilation. I also discussed the benefits of decompilation. You learned the techniques to prevent these decompilation threats. Finally, I discussed Java bytecode and the importance of type safety in detail.
Check Your Understanding Multiple Choice Questions 1. Which of the following constant pool entries is used to represent fields and methods without indicating the name of the class or interface to which they belong? a. NameAndType b. Utf8 c. String d. Integer
87
88
Part I
INTRODUCTION TO JAVA SECURITY
2. Which of the following refers to the technique of confusing the reader by hiding important details? a. Bytecode hosing b. Code obfuscation c. Salting d. Decompilation 3. Which of the following is not a category of bytecode instructions? a. Pushing constants onto the operand stack b. Accessing arrays c. Arithmetic and logical instructions d. Creating structures 4. Which of the following bytecode instructions operate on arrays? a. aaload b. getstatic c. aastore d. ldc 5. Which of the following tools converts the bytecode to a human-readable format? a. Mocha decompiler b. HoseMocha c. Assembler d. Crema
Short Questions 1. Explain the role of a decompiler. 2. Explain code obfuscation and bytecode hosing.
Answers Multiple Choice Answers 1. a. The NameAndType structure can be used to represent fields and methods without indicating the name of the class or interface to which they belong. 2. b. Code obfuscation refers to confusing the reader by hiding important details.
CLASS FILES
Chapter 4
3. d. The bytecode instructions can be divided into few basic categories, such as pushing constants onto the operand stack, accessing arrays, stack manipulation through PUSH and POP instructions, arithmetic and logical instructions, control transfer or return, invoking methods, and creating objects. 4. a and c. Bytecode instructions such as aaload and aastore operate on arrays and check the array memory bounds as well. 5. c. An assembler converts the bytecode to a human-readable format.
Short Answers 1. A decompiler converts Java bytecode to the original source code. In other words, it undoes the work done by the compiler. The decompilation process is also referred to as reverse engineering. You can use the Mocha decompiler to decompile class files into source code. Compilers generally render it ineffective for converting machine-level language to high-level language because they remove a lot of additional information such as the variable and function names. This information is needed by the decompilers to perform their tasks. In case of the java compiler, the only information lost during compilation are the comments, which cannot be retrieved by the Mocha decompiler. However, the variable and method names are self-explanatory and their functionality can be understood even without comments. Although the code generated by the Mocha decompiler is not completely similar to the Java source code, it is easily understandable and can be modified. Therefore, hackers or even Java developers can use this decompiler to leak out business secrets or decompile a competitor’s project, respectively. The decompiler is not just used by hackers for malicious purposes. You can, in turn, use it for several important reasons. For example, imagine that you delete the source code for a Java program by mistake. However, you still have the compiled class file for the particular source. Then, you can use the decompiler to recover the lost Java source code. Decompilers can also be used to check the validity of the Java class files that you receive from an unknown source. 2. Code obfuscation refers to confusing the reader by hiding important details. This is generally done by removing comments from the code. The reader can recognize the functionality of the various methods and fields by reading their meaningful names. You need not worry as you can also replace variable and method names by meaningless characters using the code-obscuring techniques. Therefore, this technique retains the compatibility of the source code in the Java environment, although the source code is not easily decipherable by human readers. This does not, however, indicate that your code is protected from all human readers. There might be some smart hackers
89
90
Part I
INTRODUCTION TO JAVA SECURITY
who might still succeed in deciphering this obscured code. This risk always remains. To protect Java bytecode from reverse engineering, you can apply a bytecode obfuscator on the specified class files. Crema is one such example of a Java obfuscator. The same author who released Mocha, the decompiler, released Crema. Crema scrambles the symbolic reference information available in class files, such as the references to the field or methods of a specified class. This makes a class file less vulnerable to decompilation. For example, Crema scrambles class, interface, superclass, fields, and method names. You might be wondering how the JVM will link to the library packages if these symbolic references are missing in a class file. Do not worry, as Crema scrambles the references but rearranges them in such a way that the JVM still functions correctly. Now, if this code were decompiled using Mocha, it would raise exceptions, as it would be confused by the jumbled symbolic references, thus rendering reverse engineering difficult. Another software that helps in suppressing reverse engineering is HoseMocha. HoseMocha is a bytecode hoser utility that appends extra instructions after a return instruction in the source code. For example, it splits the bytecode into different instruction sequences such as a PUSH operation followed by a POP operation. However, hackers can also suppress this technique. When the bytecode that has been hosed is decompiled, it results in readable code but it is spread with misleading passages. It is more readable than code after obfuscation. There is another serious implication in the use of bytecode hosers such as HoseMocha. They affect the performance of JIT compilers. You already know that JIT compilers are used to improve the performance of Java programs by reducing the interpretation overhead. JIT compilers read patterns in the bytecode, which can be converted to native code. Now, if you rearrange these patterns by using a bytecode hoser, you make the task of the JIT compiler difficult.
PART
II Advanced Java Security Concepts
This page intentionally left blank
Chapter 5 The Class Loader and the Class File Verifier
I
n the previous chapter, I discussed the class file format in detail and explained the advantages and disadvantages of the binary information contained in a class file. The class loader and class file verifier components of JVM form the core of the security implementation in Java. They work in collaboration to ensure the safe execution of Java programs. The class loader loads the specified class files in the Java program, whereas the class file verifier determines whether these class files are valid and safe for execution by JVM. In this chapter, I’ll discuss the working of the class loader and the class file verifier in detail.
Class Loaders: Basics You are already aware that class loaders are used to load classes in a running Java program. They guard the Java runtime environment by checking the bytecode being loaded so that the running Java code is not replaced by malicious code. The class loaders take care of this by separating the code downloaded from different sources and preventing the loading of untrusted classes. Class loaders mainly perform the following functions: ◆ They load the bytecode when requested by JVM. They have customized
methods to load bytecode either from the local disk or over the network. ◆ They define the namespaces for different classes and the relation between
these namespaces. ◆ They also define the set of permissions for loaded classes. Runtime access to
resources is based on these permissions. Therefore, the main function of class loaders is to load classes from different locations. However, it is not so simple as it sounds. Some considerations are necessary to enforce security while using the class loader. Let’s begin by discussing the class loader architecture in detail.
The Class Loader Architecture There is more than one class loader operating in JVM. JVM supports a flexible class loader architecture that loads classes in different ways. You are already aware that the classes loaded in a running Java program are from either trusted or untrusted sources.
THE CLASS LOADER AND THE CLASS FILE VERIFIER
Chapter 5
The trusted sources can be the Java API classes or the local file system, whereas the untrusted sources are external sources such as Web servers or the network. There are two types of class loaders, the primordial class loader and the class loader objects. These have been discussed in Chapter 3, “The Java Virtual Machine”. The primordial class loader is an integral part of the JVM implementation and is used to load trusted classes. Therefore, the classes loaded by the primordial class loader are not subject to verification. On the other hand, the class loader objects load untrusted classes in custom ways. Class loader objects are treated like any other Java object; they are created in Java, compiled into class files, and instantiated. Therefore, the classes loaded through the class loader objects are subject to the verification performed by the class file verifier. Due to the class loader objects, you need not keep track of which classes may be needed by a running program at the time of compilation. This can be done at runtime by the class loader objects.
Types of Class Loader Objects There are different types of class loader objects, as follows. ◆ Applet class loaders ◆ Secure class loaders ◆ RMI class loaders ◆ URL class loaders
The applet class loaders load classes into a browser and prevent the external classes from overriding the Java API classes. First, the applet class loaders try to load classes using the primordial class loader and only if the primordial class loader is unable to load the classes do they attempt to load classes from external sources such as the network or Web servers. If the primordial class loader is unable to load a class, the applet class loader loads the specified class via the HTTP protocol by using methods of the URL class. The specified code is searched for in the location mentioned in the CodeBase property, which is specified in the <APPLET> tag. If the class is still not found, a ClassNotFound exception is thrown. Passing the class-loading request to the parent class loader is an important feature of the parent-delegation model, which is discussed in a subsequent section. The property of class loaders that prevents external classes from overriding the Java API classes is a very important aspect for security implementation. For example, imagine an external SecurityManager class trying to replace the java.lang.SecurityManager class. This would mean trying to tamper with the security policy defined for the system, which contains the protection domain and the resource access permissions. This would cause a gaping hole in the security system.
95
96
Part II
ADVANCED JAVA SECURITY CONCEPTS
The secure and RMI class loaders are similar in function to the applet class loaders. They allow the primordial class loader to look for code before attempting to load it via external sources. The only difference is the location from where these class loaders can load classes. The secure class loader allows classes to be loaded from the directories specified in the java.app.class.path property. However, only the classes specified in the java.security package can use the secure class loaders. On the other hand, the RMI class loaders can load classes from the URL specified in the rmi.server.codebase property. The URL class loaders are used to load classes from URL search paths, which refer to both directories and JAR files. If a URL ends with a “/”, it signifies the reference of a directory; otherwise, it is assumed to refer to a JAR file. The classes loaded by the URL class loaders are by default granted the permission to access the URLs as specified in the URLClassLoader definition.
Namespaces You are already aware that there may be many class loaders running in a particular instance in JVM. However, the class loaders run in their own defined namespace. A namespace is a set of unique class names loaded by a specified class loader. The namespaces of different class loaders also overlap. Generally, JVM implementations employ different class loaders for loading code from different locations. In earlier versions of Java, the code loaded by similar class loaders was restricted by a single security policy. However, with the emergence of code signing, you could tag code with two distinct features, namely, the code origin (URL) and the code signer (the person who signed the code). The class loaders that load the specified code know about these code attributes. When applets are loaded from the network, the respective applet class loaders receive the binary information and instantiate this information as a new class. Because applets cannot define new class loaders, the applet class loaders only load the necessary code from the desired locations. The applet class loaders install the applets in different namespaces, which hide the details of an applet from the other applets. The advantage of this is that applets can have classes with similar names without it causing any name collision. When a class is transferred over the network, the applet class loader places it in a namespace, which also indicates the location from where the class was picked up. In a Java program, when one class references another, the applet class loader looks for the referenced class using a particular search pattern. It first searches in the classes loaded by the primordial class loader. If it is not found here, the applet class loader specifies the namespace of the class making the reference in its search criteria. This particular search pattern in which the built-in classes are searched first prevents imported classes from posing as built-in classes. For example, imagine an applet trying to gain access to the local file system by replacing the built-in I/O classes.
THE CLASS LOADER AND THE CLASS FILE VERIFIER
Chapter 5
A trusted Java application can define its own class loaders. However, this would be very risky in case of an untrusted applet, which could define its own namespace and trigger an attack on the Java security system. Therefore, you should be very careful while creating applications that define their own class-loading mechanisms. One drawback in the Java security model is that due to customized class loaders, the security model is distributed and cannot be controlled centrally. Therefore, while defining your own class loading mechanisms, you need to manage the namespaces associated with these class loaders as well.
Class Loader Hierarchies You are already aware that a JVM implementation can have multiple instances of class loader objects. You might be wondering that with so many instances of class loaders, which one should you actually use. Also, in Java 2, there are multiple class loader classes, all having unique features, which further complicates the issue. Java 2 implements a hierarchy of class loaders. Figure 5-1 shows the inheritance hierarchy of class loaders. You are already aware that there is only one primordial class loader and it cannot be overridden. The class loader class hierarchy starts with an abstract class, java.lang.ClassLoader, at its root. This class was defined in jdk1.0 and has been further refined across the various new version releases in Java.
FIGURE 5-1 Class loader hierarchy
97
98
Part II
ADVANCED JAVA SECURITY CONCEPTS
In Java 2, the class, java.security.SecureClassLoader, is a subclass of the abstract ClassLoader class. Further, the class, java.net.URLClassLoader, is a subclass of the SecureClassLoader class. The Appletviewer uses a private class, sun.applet.AppletClassLoader. This class was a subclass of the ClassLoader class in jdk1.0. However, in Java 2, it is a subclass of the URLClassLoader class. As I have already discussed in the preceding section, you can create subclasses from any of these class loader classes to define a custom class loader. This depends entirely on your requirements defined for the custom class loader. However, the AppletClassLoader class is a private class, and therefore, you should not create subclasses from it. In the preceding sections, I’ve discussed the intricacies of class loaders with respect to the various classes in Java. Now, I’ll discuss the actual working of these class loaders.
The Class-Loading Mechanism In a running Java application, classes are needed when referenced by the currently running class. Once the main() method starts executing, the other referenced classes in the program are loaded by the class loader. It is the task of the class loader to create a namespace containing a set of class names, which includes the name of the class loaded and the classes referenced by this class. The class loader performs this function using the loadClass() method, whose definition is as follows. loadClass(String className, boolean resolveIt);
The className string represents the class to be loaded by the class loader. The resolveIt variable indicates whether a referenced class needs to be loaded. The primordial class loader uses the default implementation of the loadClass() method. Therefore, the code for a class loader searches the class path and the ZIP files for loading classes. It looks for the java.lang.Object class in the class path. Java can change the default implementation of a class loader by simply changing the set of functions that it implements. Therefore, you can also use user-defined class loaders. However, these are very powerful in the sense that they can replace the Java API core classes with their own version classes. Therefore, the classes most susceptible to these attacks—the applets—are not allowed to instantiate their own class loaders. Let’s discuss in detail how class loaders perform this function. The class loader instance invoked by JVM when a class is referenced checks whether the class has been previously loaded. If the class has been loaded previously, the class loader checks with the security manager whether the program has the permission to access the specified class. Therefore, the class loader works in coordination with the security manager in all the class-loading phases. If the security manager denies access to a particular class, a security exception is raised.
THE CLASS LOADER AND THE CLASS FILE VERIFIER
Chapter 5
If the referenced class has not been loaded earlier, the class loader checks for the specified class in the Java API core classes and the JVM extensions. This check prevents the core classes from being replaced by external classes from other sources. The core and extension classes are loaded by the built-in class loader, which is the primordial class loader. If the specified class is still not found, it is concluded that the class does not belong to a trusted source. Now, the class loader can look for the particular class in two possible locations. First, it can look for the class in the application class path that is set by using the CLASSPATH environment variable. Second, it can look for the class in the network. However, the bytecode from both the previous locations needs to be verified by the class file verifier before the class object is created. Therefore, the class loader passes the bytecode to the class file verifier before creating an object of the particular class. If the verification test is not cleared, an exception is raised. A protection domain is set for the class object, which specifies resource access for the particular class. The main difference in the class loading mechanism of Java 2 and the earlier versions is the presence of a delegation model for loading classes. I’ll discuss this parent-delegation model in the subsequent section.
The Parent-Delegation Model In jdk1.2, there is a new parent-delegation model for class loaders. The class loaders created in the earlier versions of Java still work in jdk1.2; however, they do not take advantage of the parent-delegation model. When you create a user-defined class loader in jdk1.2, each such loader has a parent class loader associated with it. If a parent class loader is not passed explicitly to the constructor of the user-defined class loader, the system class loader takes on the role of the parent. When a new user-defined class loader is created, the parent loader is passed to the constructor. On the other hand, if an existing user-defined class loader is passed to the constructor of a new user-defined class loader, this user-defined class loader becomes the parent of the new loader. I’ll explain the parent-delegation model using the following example. Consider a Java application that creates a user-defined class loader called GrandMother. However, no class loader reference is specified in the constructor of the GrandMother class. Therefore, the primordial class loader becomes the parent of the GrandMother class loader. Further, the application creates another class loader called Mother and passes the reference of the GrandMother class loader in its constructor. Therefore, the GrandMother class loader is the parent of the Mother class loader. The application further creates a class loader called Daughter and passes the reference of the Mother class loader to its constructor. Therefore, Mother acts as a parent to the Daughter class loader. Therefore, a parent-child delegation chain is created with the primordial class loader at the top. Figure 5-2 depicts this parent-child delegation chain.
99
100
Part II
ADVANCED JAVA SECURITY CONCEPTS
FIGURE 5-2 The parent-child delegation chain
The Java application requests the Daughter class loader to load a class named java.io.WriteFile. According to the parent-delegation model, the class loader first asks the parent to load the class. The parent further asks its parent to load the class and so on. Therefore, the delegation for loading the class reaches the top of the delegation chain, which is the primordial class loader. In the example, the Daughter class loader delegates class loading to the Mother class loader, which in turn delegates it to the GrandMother class loader. Finally, the GrandMother class loader delegates it to the primordial class loader. The primordial class loader loads the specified class and passes it down the chain through the GrandMother, Mother, and Daughter class loaders. The Daughter class loader further returns the loaded class to the requesting application. In Java, it is not the case that the class loader that initiates the class-loading activity is the same as the class loader that defines the specified class. For example, the class loader that is asked to load a class, although it passes this request to another class loader in the chain, is called the initiating class loader for the specified class. The class loader that actually defines the class is called the defining class loader for the specified class. In the preceding example, GrandMother, Mother, and Daughter are the initiating class loaders for the java.io.WriteFile class, whereas the primordial class loader is the defining class loader for the java.io.WriteFile class. Consider the case when a class that does not belong to the Java core API classes needs to be loaded. In this case, the application would again request the Daughter class
THE CLASS LOADER AND THE CLASS FILE VERIFIER
Chapter 5
loader to load this class. The class loading request would pass the parent-child delegation chain and reach the primordial class loader. However, because the specified class does not belong to the core Java classes, the control would be passed back to the GrandMother class loader. I’ll assume that the specified class belongs to a package that is present in the JAR files in the standard extensions directory. The GrandMother class loader, with its unique class loader method, is able to define the specified class. Therefore, the GrandMother class loader is the defining class loader, whereas the Mother and Daughter class loaders are the initiating class loaders. The primordial class loader is the default loader in a JVM implementation. However, you can also create user-defined class loaders by customizing the class loader methods. I’ll discuss more about the user-defined class loaders in the subsequent section.
User-Defined Class Loaders Creating a user-defined class loader is not always easy because you need to keep in mind certain rules and security measures while defining a new class loading mechanism. In Java 2, this process is simplified due to the delegation model discussed in the preceding section. There are four methods of the ClassLoader class that help you to interact with JVM for loading classes. The definition of these four methods is specified in the following code statements: protected final Class defineClass(String name, byte data[], int offset, int length); protected final Class defineClass(String name, byte data[], int offset, int length, ProtectionDomain protectionDomain); protected final class findSystemClass(String name); protected final void resolveClass(Class c);
These methods should associate with the internal class loader subsystem. Consider the previous code statements. Note that the defineClass() method is overloaded. It takes a byte data[] array as a parameter. This array can hold binary data for the class whose name is specified by the name parameter. The ClassLoader object accepts binary data and stores it in the array starting from the value specified by the offset parameter and continuing till the value contained in the length parameter. For the defineClass() method without the ProtectionDomain parameter, the default protection domain is assigned to the class defined. On the other hand, for the second defineClass() method, the class defined uses the protection domain as specified by the protectionDomain parameter. Therefore, the defineClass() method creates a new class and stores it in the method area of JVM.
101
102
Part II
ADVANCED JAVA SECURITY CONCEPTS
The findSystemClass() method contains a String parameter, name, which specifies the name of the class that needs to be loaded. In jdk1.0 and 1.1, when a user-defined class loader invokes this method, it asks JVM to load the specified class via its bootstrap class loader. This method then returns a reference to the loaded class represented by the Class object. In jdk1.2, the findSystemClass() method tries to load the requested class by using the system class loader. Until now, the class loader has just loaded the classes in the method area. However, linking still needs to be performed, which is done via the resolveClass() method. This method takes an instance of the loaded class as a parameter and links it. Now, let’s understand the implementation differences between a jdk1.1 and jdk1.2 user-defined class loader.
Creating a Java 1.1 User-Defined Class Loader In jdk1.1, user-defined class loaders allowed you to load classes from all types of locations. A user-defined class loader just needs to implement the loadClass() method of the abstract class, java.lang.ClassLoader. However, it is not that simple. This method needs to perform a list of functions in case of a user-defined class loader. For example, a user-defined loadClass() method needs to check whether the class has been already loaded and whether it has been loaded by the primordial class loader. If the class has not been loaded, it has to be loaded from the specified repository, defined, and finally returned to the application that requested for it. Therefore, you need to consider all these functions for just loading classes to JVM. The design of the ClassLoader class does not provide facilities for the previous functions. If you think that you just need to define the function of class loading in a different way in the user-defined class loader, you are wrong! You need to define all these functions in a user-defined class loader as well. Let’s examine a user-defined class loader, myClassLoader, whose code listing follows. import java.io.*; import java.util.Hashtable; public class myClassLoader extends ClassLoader { // The myPath string marks the path, which myClassLoader follows to get the //
full path name of the class file, which is to be loaded private String myPath;
// Constructor public myClassLoader(String myPath) {
THE CLASS LOADER AND THE CLASS FILE VERIFIER
Chapter 5
this.myPath = myPath; } public synchronized Class loadClass(String className, boolean resolveIt) throws ClassNotFoundException { Class findResult; byte classBinData[]; // Check whether the specified class has already been loaded //
in the loaded class cache
findResult = findLoadedClass(className); if (findResult!= null) { // Return a reference to the stored class return findResult; } // Check whether the class is a system class with the primordial class // loader by using the findSystemClass() method try { findResult = super.findSystemClass(className); // Return a reference to the system class return findResult; } catch (ClassNotFoundException e) { } // Prevent the overwriting of a system class by an external class. // Attempting to load system classes only through the // primordial class loader can do this. if (className.startsWith(“java.”)) { throw new ClassNotFoundException(); } // Try to load it from the basePath directory. classBinData = loadClassFrommyPath(className); if (classBinData == null) { System.out.println(“Cannot load class: “ + className);
103
104
Part II
ADVANCED JAVA SECURITY CONCEPTS
throw new ClassNotFoundException(); } // Store binary data of the class file in the byte array. findResult = defineClass(className, classBinData, 0, classBinData.length); if (findResult == null) { System.out.println(“Error in class format: “ + className); throw new ClassFormatError(); } if (resolveIt) { resolveClass(findResult); } // Return class from myPath directory return findResult; } private byte[] loadClassFrommyPath (String className) { FileInputStream myStream; String myFile = myPath + File.separatorChar + className.replace(‘.’, File.separatorChar) + “.class”; try { myStream = new FileInputStream(myFile); } catch (FileNotFoundException e) { return null; } BufferedInputStream myBuffStream = new BufferedInputStream(myStream); ByteArrayOutputStream myOutStream = new ByteArrayOutputStream(); try { int a = myBuffStream.read();
THE CLASS LOADER AND THE CLASS FILE VERIFIER
Chapter 5
while (a != -1) { myOutStream.write(a); a = myBuffStream.read(); } } catch (IOException e) { return null; } return myOutStream.toByteArray(); } }
Let’s go over the preceding code. The myClassLoader class declares a string variable, myPath, which stores the path of the directory where the class loader looks for the specified class that needs to be loaded. First, the loadClass() method checks whether the specified class has already been loaded. This method, in turn, calls the findLoadedClass() method. The specified class name is passed as a parameter to this method, which returns the class if it is located in the local cache. JVM maintains a list of the class names that have been loaded by each class loader. This list contains the class names stored in a class loader’s namespace. Before a class is loaded, this internal list maintained by JVM is checked for the specified class name. Therefore, the class loader uses the loadClass() method to load the specified class initially. The next time, the class loader calls the findLoadedClass() method to search for the class in the internal list of JVM. If the specified class is not loaded in the class loader’s namespace, the loadClass() method checks whether the specified class is a system class. It does this by passing the class name to the findSystemClass() method. The method listing is as follows: // Check whether the class is a system class with the // primordial class loader by using the findSystemClass() method try { findResult = super.findSystemClass(className); // Return a reference to the system class return findResult; } catch (ClassNotFoundException e) { }
105
106
Part II
ADVANCED JAVA SECURITY CONCEPTS
In jdk1.1, the findSystemClass() method uses the primordial class loader that attempts to load the specified class. If the specified class is found, the findSystemClass() method returns the class, which is further returned by the loadClass() method. If the class is not found, the loadClass() method checks whether the specified class is a part of the Java API core classes by the following code: if (className.startsWith(“java.”)) { throw new ClassNotFoundException(); }
This check is very important because it prevents the internal Java classes from being overwritten by the user-defined class loader. After performing all these checks, the class loader tries loading the class through its customized implementation. The myClassLoader class does this by calling its loadClassFrommyPath() method. The method code follows: // Try to load it from the basePath directory. classBinData = loadClassFrommyPath(className); if (classBinData == null) { System.out.println(“Cannot load class: “ + className); throw new ClassNotFoundException(); }
// Store binary data of the class file in the byte array. findResult = defineClass(className, classBinData, 0, classBinData.length); if (findResult == null) { System.out.println(“Error in class format: “ + className); throw new ClassFormatError(); }
if (resolveIt) { resolveClass(findResult); }
// Return class from myPath directory return findResult; }
THE CLASS LOADER AND THE CLASS FILE VERIFIER
Chapter 5
The loadClassFrommyPath() method searches for the specified class followed by a .class extension in the directory specified by the myPath variable. If the class is not found, the loadClassFrommyPath() method returns a null value. The loadClass() method throws a ClassNotFoundException. If the class is found, the defineClass() method is invoked, which translates the binary class data into internal data structures. These data structures finally create the specified Class instance. Finally, the resolveClass() method is called, which links the Class instance defined by the defineClass() method. You can test this user-defined class loader by creating a test application. The code for this application is specified here: public class TestApp{ public static void main(String args[]){ try{ myClassLoader cl=new myClassLoader(“c:\\”); Class l=cl.loadClass(“myClassLoader”); System.out.println(“Loaded” +” “+l); }catch(Exception e){}
} }
The preceding application creates an instance of the myClassLoader class and tries loading the class file of this class itself. The output of this application is shown in Figure 5-3. Let’s observe the variations of this class loader implementation in jdk1.2.
FIGURE 5-3 The output of the TestApp.java program
107
108
Part II
ADVANCED JAVA SECURITY CONCEPTS
Creating a Java 1.2 User-Defined Class Loader In jdk1.2, the delegation model is followed for the java.lang.ClassLoader class. As discussed in the parent-delegation model, the custom class loader delegates the class loading request to its parent class loader. The parent class loader calls the findClass() method. Therefore, the custom class loader loads only the classes that cannot be loaded from the parent repository. In jdk1.2, the custom class loaders do not override the loadClass() method; instead, they override the findClass() method, which contains the custom class loading logic. The definition of the findClass() method is as follows: protected Class findClass (String className) throws ClassNotFoundException
The findClass() method accepts the name of the class to be loaded as its parameter. This method attempts to locate or produce an array of bytes that defines the specified class. If the attempt fails, the findClass() method raises a ClassNotFoundException. If the class is found, the findClass() method invokes the defineClass() method, passing the class name, the array of bytes, and a ProtectionDomain object as parameters. The defineClass() method returns the Class instance for the specified class. Let’s observe the following code, which overrides the findClass() method. import java.io.*; public class myClassLoader extends ClassLoader { // The myPath string marks the path, which myClassLoader follows to get the // full path name of the class file, which is to be loaded private String myPath; // Constructor public myClassLoader(String myPath) { this.myPath = myPath; } public myClassLoader(ClassLoader parent, String myPath) { super(parent); this.myPath = myPath; } protected Class findClass(String className) throws ClassNotFoundException {
THE CLASS LOADER AND THE CLASS FILE VERIFIER
Chapter 5
byte classBinData[]; // Attempt to load the specified class from the myPath directory. classBinData = loadClassFrommyPath (className); if (classBinData == null) { throw new ClassNotFoundException(); } // Parse it return defineClass(className, classBinData, 0, classBinData.length); } private byte[] loadClassFrommyPath (String className) { FileInputStream myStream; String myFile = myPath + File.separatorChar+ className.replace(‘.’, File.separatorChar)+ “.class”; try { myStream = new FileInputStream(myFile); } catch (FileNotFoundException e) { return null; } BufferedInputStream myBuffStream = new BufferedInputStream(myStream); ByteArrayOutputStream myOutStream = new ByteArrayOutputStream(); try { int a = myBuffStream.read(); while (a != -1) { myOutStream.write(a); a = myBuffStream.read(); } } catch (IOException e) { return null; }
109
110
Part II
ADVANCED JAVA SECURITY CONCEPTS
return myOutStream.toByteArray(); } }
Let’s examine and understand the preceding code. The myClassLoader class has two constructors. The one-argument constructor takes the myPath variable as a parameter. This variable stores the directory path in which the findClass() method looks for the specified class. This constructor invokes the superclass’s no-argument constructor, which sets the class loader’s parent to the system class loader. The two-argument constructor contains a reference to a user-defined class loader and the myPath string. Therefore, this constructor invokes the superclass’s one-argument constructor by passing the specified reference. Therefore, the superclass sets this class loader’s parent as the passed user-defined class loader instance. By using the delegation model, you can chain together multiple custom class loaders. The constructor for the java.lang.ClassLoader class lets you assign a parent class loader. Therefore, the class loading requests are delegated to the parent class loader. This leaves the child class loader with only the requests that cannot be fulfilled by the parent. If the no-argument constructor is chosen, the system class loader becomes the parent for delegation. If you compare the findClass() method with the loadClass() method defined in the jdk1.1 code, you would agree that it is much simpler to write the findClass() method than the loadClass() method. The findClass() method invokes the loadClassFrommyPath() method to load the requested class in this customized way. If the loadClassFrommyPath() method is unable to locate the requested class, it returns a null value and the findClass() method throws a ClassNotFoundException. If the loadClassFrommyPath() method locates the specified class, it returns an array of bytes, which the findClass() method passes to the defineClass() method. The defineClass() method returns a reference to the loaded class or it throws an exception. Therefore, the findClass() method uses just two parts of the loadClass() method: first, the custom method in which the array of bytes is located and loaded and second, the customized manner in which the protection domain for the specified class is defined. To summarize, the delegation design for jdk1.2 class loaders is an improvement over the earlier versions of the ClassLoader class. In the earlier versions, each ClassLoader subclass had to be retested for the class loading functionality. In other words, the logic intended for a superclass was put into a subclass instead. However, in jdk1.2, the code to implement the basic requirements of class loading need not be duplicated in the subclasses.
THE CLASS LOADER AND THE CLASS FILE VERIFIER
Chapter 5
The Class File Verifier The class file verifier works in association with the class loaders and checks the integrity of the loaded class files. The structure of class files is checked, and also whether they are consistent with the remaining class files. If the class file verifier encounters certain problems with the loaded class files, it raises an exception. You are already aware that class files can also be generated by other methods besides using the javac compiler. Because class files store just binary information, JVM cannot distinguish between a class file generated by the Java compiler and one generated by a hacker. This is the reason why all JVM implementations support a class file verifier to check that the class files created are safe for use. The main job of a class file verifier is to achieve program robustness. There are many cases when a buggy compiler or a cracker can affect the health of the JVM or a running Java program. A few of these cases are listed here. ◆ The number or type of parameters passed to a bytecode instruction is illegal.
This can lead to errors in executing the program or confusion in JVM because JVM cannot identify the correct class with the specified bytecode instruction. ◆ A class file contains illegal bytecode instructions. For example, imagine that
the compiler or a hacker generates an erroneous class file. This class file contains an instruction that causes the program to loop beyond the limit specified in a looping construct. This can cause the program to be suspended or even affect the JVM. It can even lead to a system crash or introduce a bug in the Web browser. The extent of damage caused by the instruction depends on the structure of the JVM execution engine. ◆ The class file contains instructions that cause an overflow or an underflow
of the stack. This can affect the memory management of JVM by exhausting its internal memory. The buggy class file can either pop more values to the stack than it has pushed or place more values than it has popped. ◆ Class files attempt to access the members of a class for which they do not
have sufficient access rights. In this case, an exception may be raised, and program execution will stop. This would most probably be caused by a hacker. JVM can control these cases, which are risks to program execution. It can check the stack after each bytecode instruction to remove the possibility of a stack overflow or underflow. It can also check the parameters passed to the bytecode instructions in a task. However, the previous tasks can be performed only at runtime, thereby affecting the performance of the running Java program. Therefore, it is advisable that the class file verifier catch these flaws before program execution begins.
111
112
Part II
ADVANCED JAVA SECURITY CONCEPTS
The operation of the class file verifier is distributed across four different passes. In pass one, the class file verifier checks the internal structure of the loaded class file. It checks whether the class file is safe to parse. In passes two and three, the class file verifier checks the integrity of the bytecodes contained in the class file and checks the types against the rules and semantics of the Java programming language. These two passes occur during the linking phase of a Java program. Finally, pass four occurs during dynamic linking when the symbolic references are resolved. In this pass, the class file verifier verifies the symbolically referenced fields and methods of the classes in the program. I’ll now discuss the four passes in detail in the subsequent sections.
Pass One: Internal Structure Checks for Class Files During pass one, the class file verifier checks the binary information contained in the class files. The verifier performs the following checks in this pass: ◆ The class files should start with the four-byte magic number, 0xCAFEBABE.
This magic number helps the class file verifier identify whether the class files have been tampered with or created by a hacker for malicious purposes. The parser rejects the class files in which this number is missing or has been tampered with. ◆ The class file verifier checks that the minor and major version numbers in
the class file are consistent with the current JVM implementation. ◆ The class file verifier checks the length of the components of the class file,
thereby checking that the class file has not been truncated and no extra bytes have been added to it. The components contained in the class file specify their type and length, which are used by the verifier to calculate the total length of the class file. In pass one, the class verifier checks that the sequence of bytes that define a new type is as per the Java class file format. Once this check is performed, the bytes are translated into implementation-specific data structures in the method area. Therefore, the subsequent passes are performed on the internal data structures rather than the binary data in the class file format.
Pass Two: Class File Semantic Checks In this pass, the class file verifier checks the components of the class files to make sure that they follow the semantic rules of the Java programming language. For example, a method descriptor that consists of the return type and the number and type of parameters passed to the specified method is a string. Therefore, the class file verifier checks that all the method descriptors in the class file are a valid format for strings in Java. Certain constraints are placed on classes in the Java programming language spec-
THE CLASS LOADER AND THE CLASS FILE VERIFIER
Chapter 5
ification. It is the task of the class file verifier to check that the specified class follows these constraints. In addition, the class file verifier checks that no classes have been derived from the final classes and that the final methods are not overridden. The verifier also checks that the entries in the constant_pool table are valid and the indexes refer to the correct entries in the table. The big question is: “Why does the class file verifier perform these checks that are supposed to be performed at compile time by the javac compiler?” You are already aware that class files can also be generated in other ways besides using the Java compiler. However, the class file verifier has no way of differentiating between these files because both contain binary information. What happens if a hacker has generated a particular class file intentionally? Therefore, the class file verifier needs to perform the checks described previously to confirm that the rules of the language have been followed.
Pass Three: Bytecode Check In this pass, the class file verifier checks the bytecodes contained in class files. In this pass, the class file verifier is called the bytecode verifier instead. JVM performs a data check on the bytecodes that represent the methods of a specified class. The Java methods are represented as bytecode streams that consist of many 1-byte instructions called opcodes. These opcodes are followed by one or more operands, which provide additional data to JVM to execute a specified opcode instruction. Bytecodes represent a thread of execution in JVM, and each such thread has a stack, which contains frames. When a method is invoked, the specified bytecode gets its own frame, which is a memory area where the method can store information such as parameters or the result of an intermediate computation. An operand stack is that part of the frame that contains the result of an intermediate computation. Therefore, when JVM executes an opcode, it may need to constantly refer to the operand stack or the method frame that contains the parameters and local variables.
Bytecode Verifier Checks There are many checks performed by a bytecode verifier before executing a stream of bytecodes in JVM. A few of these checks are as follows: ◆ Irrespective of the way in which an opcode is executed, the operand stack
should always contain the same number and type of items. ◆ No local variable should be accessed before it is assigned a proper value. ◆ The class members, namely the fields and methods, should contain proper
values. The class fields should always be assigned values of the correct data types. The class methods should be invoked with the correct number and type of parameters.
113
114
Part II
ADVANCED JAVA SECURITY CONCEPTS
◆ The opcodes should be valid, and for every opcode, the correct values and
types should be stored in the local variables in the frame and in the operand stack. Therefore, the bytecode verifier performs these checks on a set of bytecodes and decides whether it is safe for JVM to execute these bytecodes. These checks revolve around the set of rules specified in the Java programming language. Depending on the nature of the constraints, the bytecodes compiled by a Java program would pass the bytecode verifier checks. However, the bytecodes that are not generated by the Java compiler may also pass the bytecode verifier checks with flying colors. Therefore, the bytecode verifier is not completely foolproof. The process of a bytecode verifier is illustrated in Figure 5-4.
FIGURE 5-4 The role of bytecode verifier
THE CLASS LOADER AND THE CLASS FILE VERIFIER
Chapter 5
Pass Four: Symbolic Reference Check The last pass occurs during the dynamic linking process in which the symbolic references contained in a class file are resolved. In this pass, the classes referenced by the class file are verified; therefore, as these classes are external to the specified class file, they need to be loaded. However, these classes are loaded only when they are actually needed in the program. If a referenced class is not found by JVM, it does not raise the NoClassDefFoundError exception unless the class is actually used for the first time in an executing program. The linking depends upon the JVM implementation. If JVM performs early linking, pass four occurs immediately after pass three. On the other hand, if the JVM implementation resolves symbolic references only when they occur for the first time in the program, pass four occurs well after pass three, when the execution of the bytecodes begins. Pass four is a part of dynamic linking, which resolves the symbolic references in a specified class. A class might contain symbolic references to other classes via the fields and methods defined in the specified class.
NOTE A symbolic reference is a string that contains information that uniquely identifies the referenced field or method of a class. For example, a symbolic reference to a field contains the class name, field name, and field descriptor. On the other hand, a symbolic reference to a method contains the class name, method name, and method descriptor.
While executing bytecodes, when JVM comes across a symbolic reference to another class for the first time in an opcode, it attempts to resolve the symbolic reference. First, JVM locates and loads the referenced class. It then replaces the symbolic reference with a direct reference to the specified class. This direct reference can be an offset to the specified field or method of the referenced class. JVM needs to resolve a symbolic reference only the first time it encounters the reference in the program. The next time JVM encounters the reference, it only needs to access the specified field or method by using the direct reference. When JVM resolves a symbolic reference, it is the task of the class file verifier to check whether the given reference is valid. The validity is checked in terms of whether the class can be loaded and if the specified class contains the referenced field or method. If either of these conditions is false, the class file verifier throws an error. Let’s consider an example to explain the previous concept. Assume that your Java program has a class, House. The method of this class, openDoor(), invokes a method in the class, Door. The binary data contained in the class file for House includes the name
115
116
Part II
ADVANCED JAVA SECURITY CONCEPTS
and descriptor of the method in Door. When the method in the class, House, invokes the method in class Door, the class file verifier checks that the method in the class, Door, matches the specifications listed in the House class file. If the symbolic reference is valid, the class file verifier substitutes this reference with a direct reference such as a pointer to the specified method. However, if the reference is not valid, JVM throws a NoSuchMethodError.
Checking for Binary Compatibility You are already aware that Java programs are dynamically linked. This is the main reason why pass four of the Java compiler needs to check the class references for compatibility issues. Usually, the Java compilers recompile the classes that depend on a recently changed class. Therefore, any incompatibility issues are traced at compile time itself. However, at times, the compiler does not recompile the dependent classes. For example, when you are designing large systems where various parts of the system are split into packages, you may ignore the class linking across the various packages. Therefore, pass four of the class file verifier needs to check for binary compatibility.
Data Flow Analyzers Even after the bytecode is checked for syntax errors and binary compatibility, the task of the bytecode verifier is not complete. The bytecode verifier needs to analyze the runtime behavior of the bytecode. To perform this analysis, the bytecode verifier needs to know the status of the stack for each instruction before executing it. The number and type of items in the stack is checked. The type of local variables is also checked before executing a specific instruction. The analysis of the runtime behavior of bytecode is performed by the data flow analyzer, which needs to be initialized. The initialization phase marks each instruction as unvisited, indicating that the data flow analyzer has not yet examined the specified instruction. Then, the stack is emptied for the first instruction and the local variables are initialized according to the appropriate types. The local variables that are considered as used by the method, are declared to be containing illegal values. Finally, in the initialization phase of the data flow analyzer, the changed bit of the first instruction is set. This indicates that the analyzer needs to examine the instruction. After the data flow analyzer is initialized, it performs the following tasks. First, it checks for a JVM instruction whose changed bit is set. If it is unable to locate any such instruction, this indicates that the specified method has been successfully verified. On the other hand, if it locates such an instruction, it turns off the changed bit and imitates the effect of the instruction on the stack and the local variables. The data flow analyzer checks whether the specified instruction uses values from the stack. It checks whether the elements on top of the stack are in the correct numbers and types. If the specified
THE CLASS LOADER AND THE CLASS FILE VERIFIER
Chapter 5
instruction pushes some values on the stack, the analyzer needs to check whether there is sufficient space in the stack and needs to update the stack status to reflect the pushed values. If the specified instruction reads a local variable, the analyzer needs to check whether the specified variable contains a value of the appropriate type. The analyzer also needs to change the type of the specified variable if a value is written to it. Then, the analyzer needs to determine which instructions will possibly be executed next. This can be the first instruction of the exception handlers for the specified instruction or the target instruction of a conditional or unconditional branch. In case the current instruction is not an unconditional goto or return, the next instruction in the sequence is considered. Finally, the analyzer merges the state of the stack and the local variables after executing the current instruction with the state prior to executing the current instruction. An error can be traced if the stacks are of different sizes. All these steps are executed repeatedly until the entire method has been analyzed and no JVM instruction has its changed bit set.
Summary In this chapter, I discussed the basics of class loaders. I also discussed the types of class loaders and the core architecture of class loaders. Next, I discussed the parent-delegation model in detail. The working of class loaders and the version changes across jdk1.1 and jdk1.2 for class loader implementation were also discussed. Then, I discussed the function of the class file verifier in detail. Finally, I discussed the four passes of the class file verifier process.
Check Your Understanding Multiple Choice Questions 1. Which of the following allows classes to be loaded from the directories specified in the java.app.class.path property? a. Applet class loaders
c. RMI class loaders
b. Secure class loaders
d. URL class loaders
2. The class, java.net.URLClassLoader, is a subclass of which of the following classes? a. SecureClassLoader
c. AppletClassLoader
b. ClassLoader
d. SecurityManager
117
118
Part II
ADVANCED JAVA SECURITY CONCEPTS
3. Which of the following is the function of the loadClass() method? a. Defining a namespace containing a set of class names, which includes the name of the class loaded and the classes referenced by this class. b. Loading referenced classes. c. Checking whether a class needs to be loaded. d. Searches the class path. 4. In jdk1.2, the custom class loaders override which of the following methods? a. loadClass()
c. defineClass()
b. findClass()
d. findSystemClass()
5. In which of the following passes does the class file verifier check the integrity of the bytecodes contained in the class file? a. Pass one
c. Pass three
b. Pass two
d. Pass four
Short Questions 1. Explain the parent delegation model. 2. Explain the function of pass three of the class file verifier. 3. Explain the role of data flow analyzers.
Answers Multiple Choice Answers 1. b. The secure class loader allows classes to be loaded from the directories specified in the java.app.class.path property. 2. a. The class, java.net.URLClassLoader, is a subclass of the SecureClassLoader class. 3. a, b, and c. It is the task of the class loader to create a namespace containing a set of class names, which includes the name of the class loaded and the classes referenced by this class. The class loader performs this function using the loadClass() method, whose definition is as follows. loadClass(String className, boolean resolveIt);
The className string represents the class to be loaded by the class loader. The resolveIt variable indicates whether a referenced class needs to be loaded. The code for a class loader searches the class path and the Zip files for loading classes.
THE CLASS LOADER AND THE CLASS FILE VERIFIER
Chapter 5
4. b. In jdk1.2, the custom class loaders do not override the loadClass() method; instead, they override the findClass() method, which contains the custom class loading logic. 5. b and c. In passes two and three, the class file verifier checks the integrity of the bytecodes contained in the class file and checks the types against the rules and semantics of the Java programming language.
Short Answers 1. In jdk1.2, there is a new parent-delegation model for class loaders. The class loaders created in the earlier versions of Java still work in jdk1.2; however, they do not take advantage of the parent-delegation model. When you create a user-defined class loader in jdk1.2, each such loader has a parent class loader associated with it. If a parent class loader is not passed explicitly to the constructor of the user-defined class loader, the system class loader takes on the role of the parent. When a new user-defined class loader is created, the parent loader is passed to the constructor. On the other hand, if an existing userdefined class loader is passed to the constructor of a new user-defined class loader, this user-defined class loader becomes the parent of the new loader. 2. In pass three, the class file verifier checks the bytecodes contained in class files. In this pass, the class file verifier is called the bytecode verifier instead. JVM performs a data check on the bytecodes that represent the methods of a specified class. The Java methods are represented as bytecode streams that consist of many 1-byte instructions called opcodes. These opcodes are followed by one or more operands, which provide additional data to JVM to execute a specified opcode instruction. Bytecodes represent a thread of execution in JVM, and each such thread has a stack, which contains frames. When a method is invoked, the specified bytecode gets its own frame, which is a memory area where the method can store information such as parameters or the result of an intermediate computation. An operand stack is that part of the frame that contains the result of an intermediate computation. Therefore, when JVM executes an opcode, it may need to constantly refer to the operand stack or the method frame that contains the parameters and local variables. There are many checks performed by a bytecode verifier before executing a stream of bytecodes in JVM. A few of these checks are as follows: • Irrespective of the way in which an opcode is executed, the operand stack should always contain the same number and type of items. • No local variable should be accessed before it is assigned a proper value. • The class members, namely the fields and methods, should contain proper values. The class fields should always be assigned values of the correct data
119
120
Part II
ADVANCED JAVA SECURITY CONCEPTS
types. The class methods should be invoked with the correct number and type of parameters. • The opcodes should be valid, and for every opcode, the correct values and types should be stored in the local variables in the frame and in the operand stack. 3. The analysis of the runtime behavior of bytecode is performed by the data flow analyzer, which needs to be initialized. The initialization phase marks each instruction as unvisited, indicating that the data flow analyzer has not yet examined the specified instruction. Then, the stack is emptied for the first instruction and the local variables are initialized according to the appropriate types. The local variables that are considered as used by the method are declared to be containing illegal values. Finally, in the initialization phase of the data flow analyzer, the changed bit of the first instruction is set. This indicates that the analyzer needs to examine the instruction. After the data flow analyzer is initialized, it performs the following tasks. First, it checks for a JVM instruction whose changed bit is set. If it is unable to locate any such instruction, this indicates that the specified method has been successfully verified. On the other hand, if it locates such an instruction, it turns off the changed bit and imitates the effect of the instruction on the stack and the local variables. The data flow analyzer checks whether the specified instruction uses values from the stack. It checks whether the elements on top of the stack are in the correct numbers and types. If the specified instruction pushes some values on the stack, the analyzer needs to check whether there is sufficient space in the stack and needs to update the stack status to reflect the pushed values. If the specified instruction reads a local variable, the analyzer needs to check whether the specified variable contains a value of the appropriate type. The analyzer also needs to change the type of the specified variable if a value is written to it. Then, the analyzer needs to determine which instructions will possibly be executed next. This can be the first instruction of the exception handlers for the specified instruction or the target instruction of a conditional or unconditional branch. In case the current instruction is not an unconditional goto or return, the next instruction in the sequence is considered. Finally, the analyzer merges the state of the stack and the local variables after executing the current instruction with the state prior to executing the current instruction. An error can be traced if the stacks are of different sizes. All these steps are executed repeatedly until the entire method has been analyzed and no JVM instruction has its changed bit set.
Chapter 6 Java 2 Security Manager
I
n the previous chapter, I discussed the role of the class loader and class file verifier in the security implementation of Java. However, this implementation is incomplete without a security manager. You are already aware of the security invasions on a Java system. These can vary from system modification, whereby a program can modify the system by acquiring false permissions, to system impersonation, whereby a malicious program poses as a real program and gains access to all valuable system resources. The Java security manager can be used to eliminate these security threats. The security manager enforces restrictions based on permissions specified in policy statements. The three core elements of JVM that implement security—the class loader, class file verifier, and security manager—are interdependent. The security manager depends on the class loader for the following tasks: ◆ Loading both trusted and untrusted classes ◆ Keeping both untrusted and local classes in different namespaces ◆ Applying different protection domains to these classes ◆ Preventing local trusted classes from being overwritten by untrusted classes
The class loader also depends on the security manager to prevent a malicious applet from loading its own class loader. Finally, both of these depend on the class file verifier to ensure that the contents of the class file are syntactically correct and that there is no deviant bytecode in them. In this chapter, I’ll discuss the role of security manager and how it implements security in the system in detail.
The SecurityManager Class The java.lang.SecurityManager class has been present since jdk1.0 and its behavior has been simpler than that of the class in Java 2. The SecurityManager class contains many methods preceded with the check keyword. Each of these methods corresponds to a potentially harmful operation of any Java program. For example, the checkRead() method is called when the program is supposed to read from a specified file. If the check proceeds normally, the program will proceed with the read
JAVA 2 SECURITY MANAGER
Chapter 6
operation, otherwise the method throws a SecurityException. The check methods are typically invoked as follows: SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkRead(argument, ….); }
However, this check takes place only when the security manager is active. By default, Java applications run without any security manager. Therefore, all the operations are allowed. You can specify the security manager for your Java application by the following two methods. ◆ Specify a command-line option ◆ Activate the security manager in your application code
You can set the default security manager for your application by using the -Djava.security.manager option on the command line. The default security man-
ager imposes restrictions similar to those placed on applets, thereby preventing most of the sensitive operations from being performed. For example, if you wish to set the default security manager for your Java application, myWebApp, you use the following command. java -Djava.security.manager myWebApp
In the second method, you can activate the security manager in your application code by using the setSecurityManager() method of the System class. Consider the following code snippet: public static void main(String[] args) { SecurityManager myManager = new SecurityManager(); System.setSecurityManager(myManager)
In this example, an instance of the default SecurityManager class is created. However, a subclass of the SecurityManager class can also be used. In earlier versions of Java, customizing the SecurityManager class was very easy. All you had to do was create a subclass from the default SecurityManager class. However, there were certain problems associated with this: ◆ You had to change the Java source code to implement new security policies
because the security rules were specified in the source code. ◆ To add a custom permission, you had to define a new method in your SecurityManager subclass. This was inappropriate because this subclass already
contained inherited methods from its superclass.
123
124
Part II
ADVANCED JAVA SECURITY CONCEPTS
The Java 2 security model solves these problems by storing the permissions separately so that you don’t have to modify the source code. In Java 2, the SecurityManager class still contains the check methods that are called when a potentially harmful operation needs to be performed. However, in Java 2, the default implementation of these methods is to call the checkPermission method, which, in turn, checks whether the calling thread has the permissions to perform the specified operation. The checkPermission method is defined in the AccessController class, which is defined in the java.security package. The AccessController class refers to the policy file to decide whether a particular operation is assigned the specific permission. You’ll read more about the AccessController class and permissions in Chapter 7, “The Access Controller and Permissions in Java 2”. Now I’ll discuss the SecurityManager class and its important components. I’ll discuss the various check methods based on the type of unsafe operation on which the security manager is concentrating. For example, the security manager has different check methods for checking network operations and file system operations.
Network Operations Checks The various check methods for network operations along with their functionality are listed here: ◆ checkAccept: This method throws a SecurityException if there is no per-
mission specified in the policy file. This permission allows the calling thread to accept a socket connection from the specified host and port number. The method definition is specified as follows: public void checkAccept(String host, int port)
This method is invoked for the security manager when an instance of the ServerSocket class calls its accept() method. The checkPermission method is called with the SocketPermission as its parameter. ◆ checkConnect: This method checks whether the calling thread is allowed to
open a socket connection with the specified host and port number. The method definition is specified as follows: public void checkConnect(String host, int port)
You are already aware of the fact that the check methods call the checkPermission method and pass the specified permission as a parameter. The checkConnect method calls the checkPermission method with the SocketPermission(host+”:”+port,”connect”) or SocketPermission(host,”resolve”) method as its parameter.
JAVA 2 SECURITY MANAGER
Chapter 6
◆ checkListen: This method checks whether the calling thread can wait for a
specified connection request on the given local port. The method definition is specified as follows: public void checkListen(int port)
If the port is 0, this method calls the checkPermission method through the SocketPermission(“localhost:1024-”,”listen”) method. On the other hand, if the port is not 0, this method calls checkPermission with the SocketPermission(“localhost:”+port,”listen”) method. ◆ checkMulticast: This method throws a SecurityException if the calling
thread is not allowed to use IP multicast. The method definition is specified as follows: public void checkMulticast(InetAddress maddr)
This method calls the checkPermission method with the SocketPermission(maddr.getHostAddress(), “accept,connect”) method as its parameter. ◆ checkSetFactory: This method checks whether the calling thread is allowed to set the socket factory used by the ServerSocket or Socket class. The
method definition is specified as follows: public void checkSetFactory()
The checkPermission method is called by passing the RuntimePermission(“setFactory”) permission.
File System Checks The various file system check methods are listed here: ◆ checkDelete: This method checks whether the specified thread is supposed to delete a file. This method is invoked when a class File object tries to delete a file through its delete method. The method definition is specified
as follows: public void checkDelete(String file)
The checkPermission method is called with the FilePermission(file,”delete”) permission as its parameter. ◆ checkRead: This method checks whether the specified thread is supposed to
read a file. The method definition is specified as follows: public void checkRead(String file)
125
126
Part II
ADVANCED JAVA SECURITY CONCEPTS
The checkPermission method is called with the FilePermission(file,”read”) permission as its parameter. ◆ checkWrite: This method checks whether the specified thread is supposed
to write to a file. The method definition is specified as follows: public void checkWrite(String file)
The checkPermission method is called with the FilePermission(file,”write”) permission as its parameter.
Thread Checks There is a single thread check method called checkAccess(). The security manager invokes this method when the calling thread is not allowed to modify thread arguments. This method is invoked when an object of the Thread class calls its setDaemon, setPriority, setName, stop, suspend, and resume methods. The method definition is specified as follows: public void checkAccess(Thread myThread)
This method operates depending on whether the thread argument is a system thread. If the thread argument is a system thread, this method calls the checkPermission method with the RuntimePermission(“modifyThread”) permission as its parameter. On the other hand, if the thread argument is not a system thread, the method returns back normally. However, for applications that want more access restrictions, the policy file might need to override this method. The resulting method needs to check whether the calling thread has the RuntimePermission(“modifyThread”) permission. If this permission is specified, the method returns back. This ensures that the code granted the RuntimePermission(“modifyThread”) permission is allowed to modify any thread.
Package and Class Access Checks There are certain check methods in the SecurityManager class for avoiding dangerous operations to be performed on packages and classes. These check methods are listed here: ◆ checkPackageAccess: This method checks whether the calling thread has
permissions to access a specified package. This method is typically used by the loadClass method of class loaders. The method definition is specified as follows: public void checkPackageAccess(String myPackage)
JAVA 2 SECURITY MANAGER
Chapter 6
This method first acquires a list of restricted packages by accessing java.security.Security.getProperty(“package.access”). It then compares the myPackage parameter with this list to check whether the package name either starts with or is same as the name of a restricted package. If the parameter is not similar to a restricted package, the checkPermission method is called with the RuntimePermission(“accessClassInPackage.”+myPackage) permission. ◆ checPackageDefinition: This method checks whether the calling thread
can define classes in the specified package. This method is typically used by the loadClass method of class loaders. The method definition is specified as follows: public void checkPackageDefinition(String myPackage)
This method works in a similar fashion as the checkPackageAccess method. It first looks for the list of restricted packages and checks if myPackage starts with or is same as a restricted package. If the parameter is not similar to a restricted package, the checkPermission method is called with the RuntimePermission(“defineClassInPackage.”+myPackage) permission. ◆ checkMemberAccess: This method checks whether the calling thread is
allowed to access the members of a specified class. The method definition is specified as follows: public void checkMemberAccess(Class myClass, int a)
Based on the default policy file, access is allowed to the public members of the class. On the other hand, access is also allowed to classes that have the same class loader as the calling class. If both these conditions are not true, this method calls the checkPermission method with the RuntimePermission(“accessDeclaredMembers”) permission as its parameter.
Operating System Checks The security manager also checks the access to operating system resources during the execution of a Java program. This helps in restricting malicious Java code from tampering with operating system resources, which can lead to system crash. The check methods for controlling access to operating system resources are listed here: ◆ checkExec: This method checks whether the calling thread is allowed to
create a subprocess. The security manager invokes this method when an object of the class Runtime calls its exec method. The method definition is specified as follows: public void checkExec(String cmd)
127
128
Part II
ADVANCED JAVA SECURITY CONCEPTS
The cmd parameter specifies the system command to be executed. This method invokes the checkPermission method with the FilePermission(cmd,”execute”) permission as its parameter. ◆ checkPrintJobAccess: This method checks whether the calling thread is
allowed to initiate a print job request. The method definition is specified as follows: public void checkPrintJobAccess()
This method, in turn, invokes the checkPermission method with the RuntimePermission(“queuePrintJob”) permission as its parameter. ◆ checkSystemClipboardAccess: This method checks whether the calling
thread is allowed to access the system Clipboard. The method definition is specified as follows: public void checkSystemClipboardAccess()
This method, in turn, invokes the checkPermission method with the AWTPermission(“accessClipboard”) permission as its parameter. ◆ checkLink: This method checks whether the calling thread is allowed to
dynamically link the specified library code. The method definition is specified as follows: public void checkLink(String lib)
The string parameter passed to this method is either a library name or a complete file name. The security manager invokes this method when an object of the class Runtime calls its load and loadLibrary methods. This method invokes the checkPermission method with the RuntimePermission(“loadLibrary.”+lib) permission as its parameter.
JVM Control To avoid the risk of any malicious code trying to crash JVM, the security manager has certain check methods. These methods control the access to various components and members of JVM. The various check methods for JVM control are listed here: ◆ checkExit: This method checks whether the calling thread is allowed to
cause JVM to halt based on the specified status. The security manager invokes this method when an object of the class Runtime calls its exit method. The method definition is specified as follows: public void checkExit(int status)
If the status parameter contains 0, it indicates that the exit is successful. Any other value indicates failure of the exit operation. This method, in turn,
JAVA 2 SECURITY MANAGER
Chapter 6
invokes the checkPermission method with the RuntimePermission(“exitVM”) permission as its parameter. ◆ checkPropertyAccess: This method checks whether the calling method is
allowed to access the system property with the specified key name. This method is used when an object of the class System calls its getProperty method. The method definition is specified as follows: public void checkPropertyAccess(String key)
The key parameter is a system property key. This method invokes the checkPermission method with the PropertyPermission(key, “read”) permission as its parameter. ◆ checkPropertiesAccess: This method checks whether the calling thread is
allowed to access or modify system properties. This method is invoked when an object of the System class calls its getProperties and setProperties methods. The method definition is specified as follows: public void checkPropertiesAccess()
This method invokes the checkPermission method with the PropertyPermission(“*”, “read,write”) permission as its parameter. ◆ checkAwtEventQueueAccess: This method checks whether the calling
method is allowed to access the AWT event queue. The method definition is specified as follows: public void checkAwtEventQueueAccess()
This method invokes the checkPermission method with the AWTPermission(“accessEventQueue”) permission as its parameter. ◆ checkCreateClassLoader: This method checks whether the calling thread is
allowed to create a new class loader. The method definition is specified as follows: public void checkCreateClassLoader()
This method invokes the checkPermission method with the RuntimePermission(“createClassLoader”) permission as its parameter.
Security Functions The security manager also checks whether a specified security function can be executed or not. The security manager has a single method, checkSecurityAccess, for controlling security functions. The method definition is specified as follows: public void checkSecurityAccess(String target)
129
130
Part II
ADVANCED JAVA SECURITY CONCEPTS
This method checks whether the permission with the specified permission target name should be granted or denied. If the specified permission is granted, the method returns back; otherwise a SecurityException is raised. This method creates a SecurityPermission object for the specified permission contained in the target parameter. This object in turn invokes the checkPermission method. Now that I have discussed the SecurityManager class in detail, I’ll discuss how the security manager helps in implementing security.
How the Security Manager Works The source code consults the security manager whenever an unsafe operation is performed in JVM. The security manager considers the location of the code before taking its decisions. For example, the Java API core classes are given more freedom as compared to the classes downloaded from the Web. In other words, classes from a trusted source are not limited by major security restrictions as in the case of classes loaded from an untrusted source. The flow of operation of the security manager is as follows: ◆ A Java program makes a call to a potentially unsafe operation. ◆ The Java API code checks with the SecurityManager class whether this
unsafe operation is allowed in the system. ◆ The SecurityManager class returns a security exception if the operation is
not possible. This exception is returned to the specified Java program. ◆ If the SecurityManager class approves of the operation, it returns normally
and the Java program proceeds with the execution of the dangerous operation. Every JVM can have a single security manager installed at a time. A security manager cannot be uninstalled, except by restarting JVM. However, the active security manager can be replaced if the current program can either create an instance of the SecurityManager class or set a given instance as active. Therefore, the respective permission needs to be specified in the policy file for this class. Consider the following code statements: grant codeBase “file:/C:/myWeb” { permission java.lang.RuntimePermission “createSecurityManager”; permission java.lang.RuntimePermission “setSecurityManager”; };
JAVA 2 SECURITY MANAGER
Chapter 6
The previous code statements allow any application in the C:/myWeb directory to replace the active security manager. Therefore, the security manager checks whether the specified instructions have permissions to perform an operation. For example, suppose an instruction in your program needs to connect to a specified host through a given port. Now, the security manager needs to check whether the specified instruction has the permission to perform this operation. It does this through the checkConnect(host, port) method. If the java.net.SocketPermission is specified in the policy file, the program proceeds normally. Otherwise, a SecurityException is raised. Java-enabled applications, such as Web browsers, install a security manager during their initialization phase. This step protects the system before any malicious code is executed. The security exception raised by the security manager travels through all the methods of the thread that made the call to the potentially dangerous operation. When it reaches the topmost method, the specified thread is terminated. When considering security, you should not count just on exceptions to avoid dangerous operations from being performed by hostile applets. This is because it is possible for these hostile applets to catch the security exceptions generated by a try…finally block. The SecurityManager class can be customized through subclassing. However, applets cannot install their own security managers because this would open all doors for malicious applets. The security manager performs certain tasks that prevent untrusted applets from attacking the system or JVM. These tasks involve: ◆ Controlling the ability to exit JVM ◆ Preventing file system activities such as read and write ◆ Controlling network socket operations such as accept and connect
These tasks are performed by using the various check methods discussed in the preceding section. Figure 6-1 depicts the security manager operation. You have read in the preceding section that the security manager controls both the operating system and JVM resources. However, a program needs certain system or network resources to successfully complete its execution. These resources need to be controlled if the program has been downloaded from a remote site. I’ll now discuss the various types of attacks from malicious applets that are beyond the reach of the security manager.
Areas Not Controlled by the Security Manager A malicious applet can cause different types of attacks on a system. One of the most common categories of attacks is denial of service attacks. They are so called because they deny users the ability to use their own computers. These attacks can be posed as different types ranging from consuming available CPU cycles, using up all memory
131
132
Part II
ADVANCED JAVA SECURITY CONCEPTS
FIGURE 6-1 Security manager operation
space, or causing a system to hang. These attacks suffer the same consequence of restricting users from using their computers. Cases where applets can typically initiate these attacks are listed here: ◆ A malicious applet starts a thread with MAX_PRIORITY that competes with
the currently running threads for CPU time. ◆ A cracker can define the thread’s stop() method to null. ◆ A malicious applet involves the thread in some intensive calculations, which
eats up most of the CPU time. ◆ A malicious applet can create an infinite number of windows that would use
up both CPU time and memory. Denial of service attacks are less serious than the remaining security risks that attack the system directly. These attacks may pose a risk to the browser because they may crash it. However, they cannot tamper with a system. Therefore, many people ignore these attacks. However, it is advisable not to ignore these attacks. These can be con-
JAVA 2 SECURITY MANAGER
Chapter 6
trolled by restricting resource access, namely limiting CPU usage or restricting the number of instructions that can be run in a single program. The different types of these attacks are covered in the next few sections.
Opening Large Number of Oversized Windows Opening a large number of oversized windows might prove to be a much more serious attack as compared to the denial of service attacks. These windows render the keyboard and mouse useless when they pop up on the screen. When multiple windows are being created, few untrusted windows might pop up without any warning. These multiple windows cause the window manager’s event queue to be filled up. This disables the keyboard and mouse, which also use window events to interact with the computer. Crackers can use these windows to solve their malicious purposes. For example, imagine a cracker writes code that generates an imitation of a password window. Users unknowingly enter their user ID and password, which are captured by a malicious applet and sent to the cracker’s site, where they are stored for future use. Therefore, a user can be easily fooled through such attacks. This example shows how an attack can be posed through the loopholes in the security model.
Cycle Stealing Cycle stealing refers to any program that consumes resources without the user’s permission. There are certain difficult computational problems, which can scale exponentially. These problems could exhaust all the CPU time and memory space by stealing CPU cycles from other executing programs. Imagine a Web page containing a malicious applet that contains such code. This applet could steal cycles from the computer of any user who accesses this Web page. The applet could start a thread on the computer containing the Web page and execute the factoring or complex calculation on that computer’s CPU who has accessed the specified Web page. An applet can perform any complicated task to steal cycles from the user’s computer. This task is not limited just to computational areas. Using a malicious applet, crackers can upgrade their computers by using the combined CPU power of multiple workstations on a network.
Forging E-Mails Another attack to the system is that of forging e-mails. This can be done by talking directly to the SMTP daemon on a server’s port 25. Such an attack is possible because mail hosts use port 25 for incoming Simple Mail Transfer Protocol (SMTP) messages. Applets can also be used in forging e-mails because they are loaded across the network. Therefore, a mail-forging applet does not cause an SMTP daemon on port
133
134
Part II
ADVANCED JAVA SECURITY CONCEPTS
25 to report mail coming from the computer serving the applet. Instead, the daemon reports mail coming from the Web surfer’s computer. Therefore, this can doubly forge mail. For example, imagine that you visit a Web page where a malicious applet is run. This applet uses your computer and your account on that computer to doubly forge mail. Moreover, when this mail reaches the receiving end it does not appear to be forged at all. Therefore, forged mail can be sent to any e-mail address. By using threads and applets, a cracker can easily forge mail in the background while a user is accessing an applet unknowingly at the front. The entire process of forging mail could be understood through Figure 6-2. However, the preceding scenario does not fall under the category “Areas not controlled by a security manager” in the real sense. An untrusted applet is not allowed to connect to any other computers. Therefore, there is no question of mail forging. The security manager can control mail forging by not trusting arbitrary applets to begin with. It is purely at the discretion of the applet to breach the trust that is granted. In such cases, it is a breach of trust and not a breach of security. The attacks discussed previously are generally introduced in the system through public methods defined in a given class. Therefore, a method should be defined public only after some thought. Instead define it as protected or private. This may close the potential security holes in the system. When these changes have been made, you’ll need to check whether the applet’s functionality has been affected.
FIGURE 6-2 The mail-forging approach
JAVA 2 SECURITY MANAGER
Chapter 6
The security manager does not enforce any restrictions on these attacks, which either allocates memory or leads to thread creation. In other words, there are no checkAllocateMemory() or checkCreateThread() methods in the SecurityManager class that can control these events. Therefore, a security manager alone cannot prevent every risky operation on your computer. You are already aware that besides these attacks, the security manager provides check methods for every other potentially unsafe operation.
Writing Your Customized Security Manager From the preceding sections, you can gather that the security manager is a JVM object that can implement a security policy through the various methods defined in its class. By default, the security manager does not permit access to local system resources except for read and write permissions for the directory and subdirectories from where a specified Java program is invoked. However, you can customize the default security manager so that it can implement customized checks for your applets and applications. Note that this customized security manager must contain access verification code for every check method that you override. I’ll discuss an application that uses a customized security manager. This application prompts users for password information before they can access threads on the system. The customized security manager also looks into the fact that even though users have cleared the password check, they would still need the specific permissions in the policy file for them to access a specified thread. I’ll write a customized security manager that prompts the user for a password before allowing a specified program to access a thread. The SecurePasswordManager class contains the customized security manager implementation. Let’s discuss this class in detail. The SecurePasswordManager class has two private instance variables to contain the password string and an input buffer, which stores the user’s password input. Therefore, the SecurePasswordManager class definition is specified in the following code: public class SecurePasswordManager extends SecurityManager{ private String password; private BufferedReader myBuffReader; public SecurePasswordManager(String myPass, BufferedReader br){ super(); this.password = myPass;
Finally, at the end of the SecurePasswordManager class, you can define two overridden checkAccess() methods. The code listing for these follows: public void checkAccess(Thread a){
if(!verifyAccess()) throw new SecurityException (“No access available”); } public void checkAccess(ThreadGroup a){
if(!verifyAccess()) throw new SecurityException (“No access available”); }
Both the methods defined in the previous code call the verifyAccess() method, which prompts the user for a password. If the access does not contain a true value, the methods throw a SecurityException; otherwise the methods return back normally. Another aspect to be noted about these checkAccess() methods is that they cannot independently determine that a restricted thread access is being performed. The checkAccess() methods determine whether to allow access to the current thread or the thread or thread group passed as parameters to them.
JAVA 2 SECURITY MANAGER
Chapter 6
You can override multiple check methods to implement your customized security policy. You need not override all the check methods of the SecurityManager class. Override only those that you need to customize. However, all the check methods operate in a similar fashion. If the specified access is allowed, they return normally; otherwise they raise a SecurityException. The previous example proves that creating your own security manager is not difficult. All you have to do is create a SecurityManager subclass and override the methods that you need to customize. Which methods should you override? The answer to this question is simple. I have already segregated the check methods based on their operations in a preceding section. All you have to decide is the area on which you are performing the customized operation. For example, you are trying to customize the policy for access permissions on files, which restrict a user to read or write to a specific file. In such a case, you will override the check methods defined for the file system, namely checkDelete(), checkWrite(), and checkRead(). However, while overriding the various check methods, you need to consider all the situations in which the specific method can be invoked. You are already aware that you need to explicitly call the security manager for security checks in the system. Therefore, the specified program will declare the custom security manager in its main method. The code listing for this main method follows: public static void main(String[] args) { BufferedReader myBuffReader = new BufferedReader (new InputStreamReader(System.in)); try { System.setSecurityManager(new SecurePasswordManager (“password”, myBuffReader)); } catch(SecurityException e) { System.out.println(“Cannot replace the existing security manager!!!”); } }
Therefore, the following code statement creates a new instance of the SecurePasswordManager class with the password “password”. This instance is passed to the System.setSecurityManager() method, which installs this instance as the security manager for the running application. System.setSecurityManager(new SecurePasswordManager(“password”, myBuffReader));
137
138
Part II
ADVANCED JAVA SECURITY CONCEPTS
However, the life of this security manager is limited until the program is running. Only one instance of security manager is allowed for a running program. If the running application tries to create another instance, a SecurityException is raised. The main() method also contains a test application that creates a thread group and few threads within it. Finally, the test application starts all the threads. This application should contain instances of restricted thread access for the method to invoke the customized security manager. Consider the following code: Thread myThread[] = new Thread[3]; ThreadGroup myGroup = new ThreadGroup(“ Many Threads”); for (int i = 0; i < 3; i++) { myThread[i] = new Thread(myGroup, String.valueOf(i)); myThread[i].start(); }
The code statements defined in the for loop are restricted thread statements because they are trying to access threads. These statements will invoke the SecurePasswordManager checkAccess() methods, which, in turn, will invoke the verifyAccess() method. Finally, you’ll run the application with the specified main() method to test the security manager. You’ll be prompted to enter the password every time a thread or thread group is created or accessed. If you enter the correct password, access is granted, whereas if you enter the wrong password, the checkAccess() method throws an exception. Refer to Figure 6-3 for an example.
FIGURE 6-3 The exception raised in the TestApp program upon entering
the wrong password
JAVA 2 SECURITY MANAGER
Chapter 6
Let’s consider another example where the read and write access to a file is controlled through a custom security manager. Therefore, when you try to read or write a specified file, you are prompted for a password. If the user enters the correct password, the file access permissions in the policy file are checked. Only after these permissions have been checked will you be able to perform read and write operations in the specified files. Therefore, the custom security manager class for this application would be similar to the one discussed previously. The only difference would be the check methods that need to be overridden. In the earlier example the check methods for threads, namely checkAccess(Thread a) and checkAccess(ThreadGroup a), were overridden. In this example, the checkread() and checkwrite() methods need to be overridden. Consider the code for this example. public class SecurePasswordManager extends SecurityManager{ private String password; private BufferedReader myBuffReader; public SecurePasswordManager(String myPass, BufferedReader br){ super(); this.password = myPass; this.myBuffReader = br; } private boolean verifyAccess(){ int a; String mySecretPassword; System.out.println(“Enter your password:”); try{ mySecretPassword = myBuffReader.readLine(); if (mySecretPassword.equals(password)) return true; else return false; } catch (IOException e){ return false; } } public void checkRead(String myFile) { if((myFile.equals(File.separatorChar + “C:” + File.separatorChar + “myDir” +
The checkRead() and checkWrite() methods are overridden as shown in the bold code. The checkRead method first checks for the path /C:/myDir/readFile.txt. If the path exists, the user is prompted for the password twice. First, when the user tries to read the C:/myDir directory and second, when the user attempts to read the readFile.txt file. If the password supplied by the user is correct, the checkRead method performs the access check by calling the checkPermission method. An instance of the FilePermission class is passed as its parameter to the checkPermission method. This parameter checks whether the specified file has read permissions.
JAVA 2 SECURITY MANAGER
Chapter 6
The permissions are specified in the policy file. The checkWrite method operates similarly as the checkRead method. The policy file should have the following entries for a user to successfully read and write to the specified files. grant { permission java.io.FilePermission “C:/myDir/writeFile.txt”, “write”; permission java.util.PropertyPermission “C:/myDir”, “read”; permission java.io.FilePermission “C:/myDir/readFile.txt”, “read”; };
These examples show that to write a custom security manager, you need to create a SecurityManager subclass and override the methods that you need to customize.
Summary In this chapter, I discussed the interdependence of the three security components of JVM namely, class loader, class file verifier, and security manager. I discussed the SecurityManager class in detail. In addition, I discussed all the check methods for the SecurityManager class according to the area controlled by them. Next, I focused on how the security manager is invoked in a Java application. The security manager cannot control certain areas. These areas pose the problem of security threats. This was also discussed in this chapter. Finally, I discussed how you can customize the SecurityManager class to limit resources based on your requirements.
Check Your Understanding Multiple Choice Questions 1. Which of the following statements is true? a. By default, Java applications run without any security manager. b. By default, Java applications run with a security manager. c. If the check does not proceed normally, the method throws a SecurityException. d. The SecurityManager class contains many methods preceded with the check keyword.
141
142
Part II
ADVANCED JAVA SECURITY CONCEPTS
2. Which of the following methods is used to specify the security manager for your Java application? a. Specify a command-line option. b. Use the -Djava.security.manager option. c. Invoke the check methods. d. Activate the security manager in your application code. 3. Which of the following methods are used to check network operations? a. checkConnect() b. checkListen() c. checkSetFactory() d. checkSecurityAccess() 4. What is the function of the checkPropertiesAccess() method? a. This method determines whether the calling method is allowed to access the system property with the specified key name. b. This method determines whether the calling thread is allowed to access the members of a specified class. c. This method determines whether the calling thread is allowed to access or modify system properties. d. This method determines whether the permission with the specified permission target name should be granted or denied. 5. Which of the following areas aren’t controlled by the security manager? a. Creating subprocesses. b. Opening large number of oversized windows. c. Performing intensive calculations. d. Accessing or modifying system properties.
Short Questions 1. Explain the process of specifying the security manager for your Java application. 2. Compare the checkPropertyAccess() and checkPropertiesAccess() methods. 3. How does opening a large number of windows affect CPU time and memory?
JAVA 2 SECURITY MANAGER
Chapter 6
Answers Multiple Choice Answers 1. a, c, and d. The SecurityManager class contains many methods preceded with the check keyword. If the check proceeds normally, the corresponding check method throws a SecurityException. By default, Java applications run without any security manager. 2. a, b, and d. You can specify the security manager for your Java application by specifying a command-line option and activating the security manager in your application code. You can set the default security manager for your application by using the -Djava.security.manager option on the command line. 3. a, b, and c. The checkConnect() method determines whether the calling thread is allowed to open a socket connection with the specified host and port number. The checkListen() method determines whether the calling thread can wait for a specified connection request on the given local port. The checkSetFactory() method determines whether the calling thread is allowed to set the socket factory used by the ServerSocket or Socket class. 4. c. The checkPropertiesAccess() method determines whether the calling thread is allowed to access or modify system properties. 5. b and c. Security manager has the checkPropertiesAccess() method, which controls access on system properties. The checkExec() method determines whether the calling thread is allowed to create a subprocess.
Short Answers 1. You can specify the security manager for your Java application by the following two methods. • Specifying a command-line option • Activating the security manager in your application code You can set the default security manager for your application by using the Djava.security.manager option on the command line. The default security manager imposes restrictions similar to those placed on applets, thereby preventing most of the sensitive operations from being performed. For example, if you wanted to set the default security manager for your Java application, myWebApp, you would use the following command. java -Djava.security.manager myWebApp
143
144
Part II
ADVANCED JAVA SECURITY CONCEPTS
In the second method, you can activate the security manager in your application code by using the setSecurityManager() method of the System class. Consider the following code snippet: public static void main(String[] args) { SecurityManager myManager = new SecurityManager(); System.setSecurityManager(myManager)
2. The checkPropertyAccess() method determines whether the calling method is allowed to access the system property with the specified key name. This method is used when an object of the class System calls its getProperty() method. The method definition is public void checkPropertyAccess(String key). The key parameter is a system property key. This method invokes the checkPermission() method with the PropertyPermission(key, “read”) permission as its parameter. The checkPropertiesAccess() method checks whether the calling thread is allowed to access or modify system properties. This method is invoked when an object of the System class calls its getProperties() and setProperties() methods. The method definition is public void checkPropertiesAccess(). This method invokes the checkPermission() method with the PropertyPermission(“*”, “read,write”) permission as its parameter. 3. Opening a large number of oversized windows might prove to be a much more serious attack as compared to the denial-of-service attacks. These windows render the keyboard and mouse useless when they pop up on the screen. When multiple windows are being created, a few untrusted windows might pop up without any warning. These multiple windows cause the window manager’s event queue to be filled up. This disables the keyboard and mouse, which also use window events to interact with the computer. Crackers can use these windows to solve their malicious purposes. For example, imagine a cracker who writes code that generates an imitation of a password window. Users unknowingly enter their user IDs and passwords, which are captured by a malicious applet and sent to the cracker’s site, where they are stored for future use. Therefore, a user can be easily fooled through such attacks.
Chapter 7 The Access Controller and Permissions in Java 2
I
n the previous chapter, you learned about the security manager and the access controller, which was introduced in Java 2. In earlier versions of Java, the security manager alone created customized security policies for the system. However, in Java 2, the security manager passes on the access control decisions to the access controller. The Java API still calls the check methods of the security manager to enforce security. However, these methods, in turn, call the checkPermission() method of the AccessController class. Therefore, the access controller provides a single method for applying fine-grained access control. The access controller also determines the permissions needed by a program to access resources. I’ll discuss the role of the access controller and permissions in detail in this chapter.
The AccessController Class The java.security package contains the AccessController class in Java 2 that performs the following functions: ◆ It refers to the default security policy to determine whether access to a valu-
able system resource is allowed. ◆ It marks certain code as “privileged”, thereby affecting the resource access
permissions defined for the code. ◆ It stores a picture of the current calling context. This helps the access con-
troller to take access control decisions from a different context with respect to the current context. You are already aware of the checkPermission() method of the AccessController class. Let’s recall it. The checkPermission() method determines whether the resource access request by a specific permission is to be granted. If the access to a requested resource is granted, the checkPermission() method returns back. On the other hand, if the access is denied, AccessControlException is thrown. In the subsequent section, I’ll discuss the methods of the AccessController class.
THE ACCESS CONTROLLER AND PERMISSIONS IN JAVA 2
Chapter 7
Methods of the AccessController Class The following is the list of methods in the AccessController class: ◆ checkPermission(Permission perm) ◆ doPrivileged(PrivilegedAction action) ◆ doPrivileged(PrivilegedAction action, AccessControlContext context) ◆ doPrivileged(PrivilegedExceptionAction action) ◆ doPrivileged(PrivilegedExceptionAction action, AccessControlContext context) ◆ getContext()
Now, let’s discuss these methods in detail.
The checkPermission(Permission perm) Method I have already discussed the checkPermission(perm) method in brief. Now, let’s discuss it in detail. The definition for this method is specified here: public static void checkPermission(Permission perm) throws AccessControlException
AccessControlException is thrown only if the permission contains an invalid value
or is of an incorrect type. This indicates that it is an unchecked exception. Consider a scenario where the current thread passes through x callers starting from callers 1 to x. Then, the checkPermission() method is invoked by caller x. The checkPermission() method decides the resource access status on the basis of the following algorithm: i=x; while(i>0) { if(caller i does not have the specified permission in its protection domain) throw AccessControlException else if(caller i contains privileged code) { if(doPrivileged method with context parameter is called) context.checkPermission(perm) return;
147
148
Part II
ADVANCED JAVA SECURITY CONCEPTS
} i=i-1; }; //Check the context inherited on creation of a new thread. // AccessControlContext associated with the new thread is // stored as the inherited context. inheritedContext.checkPermission(permission);
If the caller is marked as privileged, the checkPermission() method stops checking the code and returns back. A caller is marked as privileged via a doPrivileged() call. The usage of the doPrivileged() method is discussed in the subsequent sections.
The doPrivileged(PrivilegedAction action) Method The definition for this method is specified here: public static Object doPrivileged(PrivilegedAction action)
This method performs the specified PrivilegedAction action with all the privileges enabled. The specified action is performed with all the permissions specified in the protection domain of the calling function. The parameter passed to this method is the action that needs to be performed. This method returns the value retuned by the run() method of the specified action.
NOTE The PrivilegedAction interface object is used to perform computations with privileges enabled. The specified computation is performed by invoking the AccessController.doPrivileged() method on the PrivilegedAction object. This interface is used only for those computations that do not throw checked exceptions.
The doPrivileged(PrivilegedAction action, AccessControlContext context) Method The definition for this method is specified here: public static Object doPrivileged(PrivilegedAction action, AccessControlContext context)
This method performs the specified PrivilegedAction action with all the privileges enabled. However, the action is also restricted by the specified AccessControlContext
THE ACCESS CONTROLLER AND PERMISSIONS IN JAVA 2
Chapter 7
context. When the specified action is performed, it is controlled by both the permissions specified by the protection domain of the caller and those specified by the AccessControlContext domain. As seen in the preceding syntax, there are two parameters passed to this method, namely action and context. Here, action specifies the action to be performed; context indicates an access control context, which specifies the restrictions to be applied to the privileges of the caller domain before performing the specified action. The method returns the value as specified by the run method of the action.
The doPrivileged(PrivilegedExceptionAction action) Method The definition for this method is specified here: public static Object doPrivileged(PrivilegedExceptionAction action) throws PrivilegedActionException
This method performs the specified PrivilegedExceptionAction action with privileges enabled. The action is performed with all the permissions specified in the protection domain of the caller. The parameter passed to this method is the action to be performed and it returns the value specified by the run method of the action.
NOTE The PrivilegedExceptionAction interface is used only for those computations that throw checked exceptions. Computations that do not throw checked exceptions should use the PrivilegedAction instead.
The doPrivileged(PrivilegedExceptionAction action, AccessControlContext context) Method The definition for this method is specified here: public static Object doPrivileged(PrivilegedExceptionAction action, AccessControlContext context) throws PrivilegedActionException
This method performs the specified PrivilegedExceptionAction action with all the privileges enabled. However, the action is also restricted by the specified AccessControlContext context. When the specified action is performed, it is controlled both by the permissions specified by the protection domain of the caller and those specified by the AccessControlContext domain.
149
150
Part II
ADVANCED JAVA SECURITY CONCEPTS
The getContext() Method The definition for this method is specified here: public static AccessControlContext getContext()
This method captures the current calling context. It captures the inherited AccessControlContext context of the current thread, and places it in an AccessControlContext object. It finally returns this object. I’ve discussed the various methods of the AccessController class in this section. Now, let’s discuss how to write privileged code by using these methods.
Writing Privileged Code The caller of a specified resource request can be marked as privileged. The checkPermission() method makes access control decisions; however, it stops checking if the caller is marked as privileged. The caller is marked as privileged by calling the doPrivileged() method without the context argument. If the protection domain of the caller contains the specified permission, the checkpermission() method returns back normally. This indicates that no further checking is done and the resource is allocated to the specified caller. However, if the specified permission is missing in the protection domain of the caller, an exception is raised. Consider a scenario when you do not need to return any value from the privileged block. Observe the outline code for such a situation: callerMethod() { normal code statements AccessController.doPrivileged(new PrivilegedAction() { public Object run()
{
//add privileged code here return null; } }); normal code statements
}
In the preceding code, the PrivilegedAction instance acts as an interface with the run() method that returns an Object. Therefore, when the doPrivileged() method is called, this instance is passed to it as a parameter. The doPrivileged() method
THE ACCESS CONTROLLER AND PERMISSIONS IN JAVA 2
Chapter 7
takes an instance of the PrivilegedAction class and invokes its run() method in privileged mode. The run() method is called after enabling privileges. The privileges are revoked after the run() method is executed. The preceding code can also be implemented without using the inner class. In this case, you create a class based on the PrivilegedAction class and pass an instance of this class to the doPrivileged() method. The implementation of such privileged code is given here: callerMethod() { //normal code statements tryAction act = new tryAction(); //assign privileges to this instance AccessController.doPrivileged(act); } class tryAction implements PrivilegedAction { public tryAction() {}; public Object run() { //add privileged code here System.loadLibrary(“awt”); return null; } }
You need to modify the inner classes based on the contents of the privileged code. For example, if the privileged code accesses local variables or throws an exception, you need to make changes to the preceding code snippets. Let’s discuss the case when privileged code needs to return values. There are multiple ways of writing privileged code that returns certain values such as the system property, user.home, which represents the home directory of the user. Consider the code given here: callerMethod() { //normal code statements String userHome = (String) AccessController.doPrivileged (new PrivilegedAction() { public Object run()
{
151
152
Part II
ADVANCED JAVA SECURITY CONCEPTS
//add privileged code here return System.getProperty(“user.home”); } }); normal code statements
}
The preceding code returns the home directory of the user by the doPrivileged() method. However, the preceding code requires a dynamic cast on the value returned by this method. You can avoid this cast by using a class that handles type casting for you. The code for such a class is specified here: callerMethod() { //normal code statements GetSystemProperty gsp = new GetSystemProperty (“user.home”); AccessController.doPrivileged(gsp); String userHome = gsp.fetchValue(); //normal code statements }
class GetSystemProperty implements PrivilegedAction { private String sysproperty; private String propvalue; public GetSystemProperty (String sysprop) { sysproperty = sysprop;} public Object run() { propvalue = System.getProperty(sysproperty); return propvalue; } public String fetchValue() {return propvalue;}
}
In the preceding section, you must have noted that the doPrivileged() method is overloaded. If the run() method throws an exception, you can use the doPrivileged() method with the PrivilegedExceptionAction interface. Let’s consider the code snippet given here: callerMethod() throws FileNotFoundException{ //normal code statements try{
In the preceding code, the getException() method returns an Exception object. This object is cast to the specific exception to be thrown. In the preceding code, it is cast to FileNotFoundException. This is necessary because only checked exceptions can be wrapped in PrivilegedActionException. Checked exceptions are exceptions specified in the throws clause Therefore, PrivilegedActionException is a wrapper for exceptions thrown by privileged actions. Now, let’s consider the case when you need to access local variables in privileged code. The local variables that you use in an anonymous inner class need to be declared as final. Consider the code given here: callermethod() { //normal code statements here final String libr = “awt”; AccessController.doPrivileged(new PrivilegedAction() { public Object run() { // add privileged code here System.loadLibrary(libr); return null; } }); //normal code statements here }
However, if the libr variable needs to be modified multiple times in the program, it cannot be declared as final. In such a case, you can create a new variable before invoking the doPrivileged() method, declare it as final, and set it equal to the earlier variable. Observe the code given here:
153
154
Part II
ADVANCED JAVA SECURITY CONCEPTS
callermethod() { //normal code statements here String libr = “awt”; // libr cannot be declared as final because it is set to //
different values multiple times. final String flibr = libr; AccessController.doPrivileged(new PrivilegedAction() { public Object run() { // add privileged code here System.loadLibrary(flibr); return null; } }); //normal code statements here }
Now, I’ll discuss permissions in Java, which also help in fine-grained access control.
Permissions in Java 2 Permissions specify the access to various system resources. The various permissions are derived from the abstract class, java.security.Permission. You need to define permissions for accessing various system resources in a Java program. For example, if a Java program attempts to write to the file myFile from the myDocs directory, you need to provide the following permission in the program for error-free execution. perm = new java.io.FilePermission(“/myDocs/myFile”, “write”);
Subclassed permissions do not belong to a common package; instead they belong to packages depending on their function. In the preceding example, note that FilePermission belongs to the java.io package. You can add new application-specific permissions in addition to the built-in permissions. I’ll discuss this in a subsequent section. Now I’ll discuss more about the built-in permission classes in Java 2.
Built-in Permissions You are already aware that most of the permissions are derived from the abstract class, java.security.Permission. In addition to this class, there is an abstract class java.security.PermissionCollection and a final class java.security.Permissions. An instance of the PermissionCollection class holds permissions of the same type. It
THE ACCESS CONTROLLER AND PERMISSIONS IN JAVA 2
Chapter 7
is used to store Permission objects of a single category such as Net permissions. On the other hand, the Permissions class is a collection of PermissionCollection objects. Now, I’ll discuss the syntax of the built-in permissions in Java 2.
The java.security.Permission Class The Permission class acts as a parent for all permissions. It contains the basic functionality needed for all permissions. All permissions have a name and abstract functions for defining the particular Permission subclass. Most permission objects also contain an actions list that specifies the actions permitted for the object. For example, a java.io.FilePermission object contains the target at which the permission is aimed, such as the file name, and the action for which the permission is required, such as read or write. However, the actions list is optional for certain Permission objects, such as java.lang.RuntimePermission objects. All the subclasses created from the Permission class must implement the implies() method, which is used to compare permissions. For example, consider the code statement given here: permission p1 implies permission p2
The preceding statement indicates that if a program is granted permission p1, it is also granted permission p2. The built-in classes that are directly derived from the Permission class are as follows: ◆ java.security.AllPermission ◆ java.net.SocketPermission ◆ java.io.FilePermission ◆ java.security.UnresolvedPermission ◆ java.security.BasicPermission
Now, I’ll discuss each of these in detail.
The java.security.AllPermission Class This class implies all permissions. It also includes the permissions that might be added later. System administrators generally use this permission because they perform multiple tasks, most of which require permissions. Therefore, it would be redundant if the security policy had to iterate through all the permissions. The AllPermission class disables security restrictions by granting all the permissions. Therefore, this class should be used sparingly and with much care. Security considerations should be kept in mind before granting this permission. This permission should be granted only to
155
156
Part II
ADVANCED JAVA SECURITY CONCEPTS
trusted applets or applications where adding all permissions would make the security policy file bulky.
The java.net.SocketPermission Class This permission indicates access to a network through sockets. An instance of the SocketPermission class contains a host specification and the actions, which specify connection with the host. The host specification includes the host name and the port range. The actions on sockets include accept, connect, listen, and resolve. The accept and connect actions should be granted permissions after much thought because this means opening doors to malicious code for entering and attacking your system.
The java.io.FilePermission Class This class indicates the permissions for actions on files. The class contains a path name and a set of actions for the file specified in the path name. The path name indicates the file or directory, which is subject to the specified actions. If the path name contains directory/*, it indicates all the files in the specified directory. An asterisk (*) represents all the files in the current directory. The <> string specifies all the files in the file system. Permissions for actions represented by the <> string must be considered for security before being granted. The actions specified in the FilePermission class are read, write, delete, and execute.
Consider the following code statement: FilePermission p = new FilePermission(“/myDocs/myDir/”, “read”);
The preceding code statement only lists all the files in the specified directory. It does not allow you to read all the files in the specified directory. For this you’ll have to modify the preceding code statement as given here: FilePermission p = new FilePermission(“/myDocs/myDir/*”, “read”);
NOTE By default, code has permissions to read files and directories from its originating URL location. You don’t need to explicitly specify permissions for these files, which reside in the same directory as the source code.
THE ACCESS CONTROLLER AND PERMISSIONS IN JAVA 2
Chapter 7
The java.security.UnresolvedPermission Class When a policy is initialized, there are certain permissions whose actual Permission class does not exist at this time. Such permissions are called unresolved permissions. This situation generally arises when the code that implements the specified permission class is not loaded in the Java environment. Therefore, the UnresolvedPermission class stores information about these unresolved permissions in the policy file. When the checkPermission() method is called in the case of an unresolved permission, it is checked to determine whether the associated class has been loaded. If the class has been loaded, the permissions of the specified type are resolved. In other words, a new object of the loaded type is instantiated with the information contained in the UnresolvedPermission class and this object replaces the UnresolvedPermission instance. If the UnresolvedPermission instance cannot be resolved, the permission is considered invalid.
The java.security.BasicPermission Class The BasicPermission class is used as the base class for classes that want the same naming convention as this class. The name for an instance of this class is same as the name of the specified permission. This class is used as the base class for permissions that have only names and no action list. However, the subclasses that are derived from this class can implement actions, if required. Following are the classes that are derived from the Permission class through the BasicPermission class. ◆ java.lang.RuntimePermission ◆ java.security.SecurityPermission ◆ java.util.PropertyPermission ◆ java.net.NetPermission ◆ java.io.AWTPermission ◆ java.lang.reflect.reflectPermission ◆ java.io.SerializablePermission
Now, I’ll discuss these permissions in detail.
The java.lang.RuntimePermission Class In this class, there is no action associated with the target name specified in the constructor. For example, RuntimePermission(“modifyThread”) grants permission to
157
158
Part II
ADVANCED JAVA SECURITY CONCEPTS
modify the current thread. The RuntimePermission class takes the following target names: ◆ exitVM ◆ modifyThread ◆ stopThread ◆ modifyThreadGroup ◆ queuePrintJob ◆ createClassLoader ◆ getClassLoader ◆ setContextClassLoader ◆ createSecurityManager ◆ setSecurityManager ◆ setFactory ◆ setIO ◆ getProtectionDomain ◆ readFileDescriptor ◆ writeFileDescriptor ◆ loadLibrary.{library name} ◆ accessClassInPackage.{package name} ◆ defineClassInPackage.{package name} ◆ accessDeclaredMembers.{class name}
The java.security.SecurityPermission Class This class is also derived from the BasicPermission class and does not contain any specified actions. The SecurityPermission class controls the access to Policy, Security, Provider, Signer, and Identity objects. It is basically used to assign security permissions. The target name contained in a SecurityPermission class instance is a security configuration parameter. A few of these parameters are listed here: ◆ setIdentityPublicKey ◆ setIdentityInfo ◆ printIdentity ◆ addIdentityCertificate ◆ removeIdentityCertificate ◆ getPolicy
The java.util.PropertyPermission Class The PropertyPermission class is one of the subclasses of the BasicPermission class, which implements actions as well. This class is used for setting property permissions. The target names for this class are names of various Java properties, such as java.home and os.name. You can also use wildcards while specifying property names. However, please note that the wildcard can occur only once and be at the extreme right position. For example, * signifies any property and c.* signifies any property name prefixed with “c”. The actions associated with the PropertyPermission class are read and write. The read action allows the call to the getProperty method that gets the specified property value. The write action allows the call to the setProperty method to set the specified property value.
CAUTION Code should be granted permissions to access certain system properties only after considering their usage. For example, imagine malicious code having access to the java.home, user.name, and user.home system properties. This indicates that the malicious code has access to sensitive information regarding the system and user environment and can tamper or crash the system.
The java.net.NetPermission Class The NetPermission class is used to assign various network permissions. Like all other subclasses of the BasicPermission class, the NetPermission class does not have any actions associated with it. It only contains target names and no associated actions. The targets supported by this permission are listed here: ◆ requestPasswordAuthentication ◆ specifyStreamHandler ◆ setDefaultAuthenticator
159
160
Part II
ADVANCED JAVA SECURITY CONCEPTS
The java.io.AWTPermission Class The AWTPermission class is used for assigning Abstract Window Toolkit (AWT) permissions. This permission class only contains the target names and no associated actions. The target name is the name of the specified AWT permission. Following is the list of such target names: ◆ accessEventQueue ◆ accessClipboard ◆ showWindowWithoutWarningBanner ◆ readDisplayPixels ◆ listenToAllAWTEvents
The java.lang.reflect.reflectPermission Class This permission class is used for reflective operations. It is a named permission with no defined actions. It has just one target name specified, suppressAccessChecks, which suppresses the Java language access checks. These access checks are public, private, and protected and are performed by reflected objects.
The java.io.SerializablePermission Class This class is used for assigning serializable permissions with target names and no specified actions. The target names for this class are as follows: ◆ enableSubstitution ◆ enableSubclassImplementation
Permission Implications You are already aware of the fact that permission are derived from the Permission class with the implies() method. This method compares permissions and defines the relation of a particular permission class with remaining permission classes. For example, consider the following code statement: java.io.FilePermission(“/myDocs/*”,“read”);
The preceding code statement implies the permission, java.io.FilePermission(“/myDocs/myFile”,“read”); however, it does not imply the java.net.SocketPermission class. There are layers of implication, which are not as direct as the ones discussed previously. For example, consider an application that has been assigned the permission to
THE ACCESS CONTROLLER AND PERMISSIONS IN JAVA 2
Chapter 7
write to the entire file system. This implies that the specified application has access to all Java system files including the Java runtime environment. Therefore, you do not need to assign a specified PropertyPermission for this. The specified application has indirectly been granted full permissions. There are various examples, which further implicate the permissions granted to a specified applet or application. Therefore, a particular permission simultaneously grants access to resources associated with it. For example, if you assign the runtime permission to modify threads, you also assign the remaining permissions associated with this action. Other dangerous permissions include those that allow you to set system properties or those that allow you to specify runtime permissions for specifying packages. AllPermission is the most dangerous of them all. Therefore, before assigning permissions to applets or applications, you need to consider the serious implication behind doing so. In the subsequent section, I’ll discuss how to create custom permission types.
Custom Permission Types The built-in permission types cover almost all of the valued resources with which JVM can interact. However, there are situations when you might need to create custom permission types to extend the capabilities of the predefined permission types. You can create custom permission types by extending one of the basic permission types. You generally extend the Permission or BasicPermission class. Then, you need to concentrate on implementing the implies(), equals(), or hashCode() methods of these classes. Finally, the new class is included in the application package. The code of the application calls the access controller when a resource request is placed. Then, the AccessController.checkPermission() method is called. In earlier versions of Java, when you had to check a new resource access, a new method had to be added to the security manger. However, in Java 2, all you have to do is create a customized Permission class for the same. Let’s consider a scenario for creating custom permission types. Imagine a banking scenario where the customer account information is considered sensitive. Not everyone has access to this sensitive information. Therefore, this information needs to be protected by extra permission types. I’ll discuss creation of a custom permission type to protect this sensitive data. I’ll create a custom permission type called MyAccPermission, which protects access to a customer account file MyAccFile in the C:\AccInfo\ directory. Then, I’ll create a test
161
162
Part II
ADVANCED JAVA SECURITY CONCEPTS
program that checks this custom permission type. The default policy file needs to be updated to contain the FilePermission and the custom permission, MyAccPermission. Following is the code listing for the MyAccPermission.java file: import java.security.*; import java.io.*; public class MyAccPermission extends BasicPermission { public MyAccPermission(String name) { super(name); System.out.println(“The MyAccPermission constructor (String name) is called”); } public MyAccPermission(String name, String actions) { super(name); System.out.println(“The MyAccPermission constructor (String name, String actions) is called”); } }
Note that in the preceding code the custom permission is extended from the BasicPermission class and not the Permission class. This is because the implementation of the BasicPermission class is much simpler as you have to only provide the target specification. In addition, this class also provides a default implementation of the Permission.implies() method. However, you need to specify two constructors in the custom permission type definition. The preceding code specifies a constructor that contains only the target name and another that contains both the target name and the actions associated with it. Both these constructors are called, although you used only one to instantiate a MyAccPermission object. The second constructor is used by the Policy object to instantiate objects of the new permission class. I’ll write a test program, which will test the new permission, MyAccPermission. import java.io.*; import java.security.*; public class TestAccPermission { public static void main(String args[])
THE ACCESS CONTROLLER AND PERMISSIONS IN JAVA 2
Chapter 7
{ try { SecurityManager MyManager = System.getSecurityManager(); if(MyManager != null) MyManager.checkPermission(new MyAccPermission (“TestAccPermission”)); File inFile = new File(“C:\\AccInfo\\MyAccFile.txt”); FileInputStream fis = new FileInputStream(inFile); InputStreamReader isr = new InputStreamReader(fis); BufferedReader br = new BufferedReader(isr);
The main() method of the preceding test program determines whether the security manager is null. The custom permission is accessed only if the security manager is not null. Note that in the preceding code, the checkPermission() method performs security checks in the currently executing thread. The parameter passed to this method is the requestor of a resource. This method checks the current security policy for the specified permission. If the permission exists, the method returns back normally; otherwise it raises an AccessControlException. The policy file should contain the following grant entries for error-free execution of the test program: grant codeBase “file:/C:/AccInfo/” { permission java.io.FilePermission “C:\\AccInfo\\MyAccFile.txt”, “read”; permission MyAccPermission “TestAccPermission”; }
163
164
Part II
ADVANCED JAVA SECURITY CONCEPTS
The preceding entry signifies that the test program would execute even if the security manager were not specified. However, the custom permission will be accessed only if the current security manager is not null. Now, I’ll execute the test program without invoking the security manager by typing the following statement at the command prompt: java TestAccPermission
The output is shown in Figure 7-1. The preceding output confirms that the test program works even without the security manager. However, the constructors of the MyAccPermission class are not called. This is because the custom permission, MyAccPermission, is called only when the security manager is not null. Now, I’ll execute the test program with the default security manager by typing the following code statement at the command prompt: java -Djava.security.manager TestAccPermission
The output is shown in Figure 7-2. Note that AccessControlException is raised on execution of the test program with the security manager. Recall that the grant entry in the policy file contained the text, codeBase “file:/C:/AccInfo/”, indicating that the permissions specified in the grant entry were assigned to the AccInfo directory.
FIGURE 7-1 The output of the TestAccPermission.java file without
invoking the security manager
THE ACCESS CONTROLLER AND PERMISSIONS IN JAVA 2
Chapter 7
FIGURE 7-2 The output of the TestAccPermission.java file while
using the security manager
However, I was executing the test program from the C:\ as can be seen in Figure 72. Therefore, for error-free execution of the program, the custom permission, MyAccPermission, and the test program, TestAccPermission, need to be moved to the AccInfo directory. Now, I’ll execute the test program from the AccInfo directory. The output is shown in Figure 7-3. As seen in Figure 7-3, the constructors of the MyAccPermission class are invoked and the contents of the MyAccFile.txt are displayed.
FIGURE 7-3 The error-free execution of the TestAccPermission.java
file while using the security manager
165
166
Part II
ADVANCED JAVA SECURITY CONCEPTS
Summary In this chapter, I discussed the AccessController class along with its methods. I also explained how to write privileged code. Then, I talked about permissions in Java 2 and also discussed all the built-in permission classes in Java. Next, I talked about the implications of assigning permissions. Finally, I explained the creation of custom permission types in Java.
Check Your Understanding Multiple Choice Questions 1. What happens if the caller of the checkPermission() method is marked as privileged? a. AccessControlException is thrown. b. The checkPermission() method stops checking the code. c. The specified PrivilegedExceptionAction action is performed with privileges enabled. d. The resource access status is not checked. 2. Which of the following method(s) marks a caller as privileged? a. checkPermission() b. getContext() c. doPrivileged() d. run() 3. Which of the following statement(s) is true? a. Permissions are derived from the java.security.PermissionCollection class. b. An instance of the Permissions class holds permissions of the same type. c. The Permissions class is a collection of PermissionCollection objects. d. The java.security.Permissions class is a final class. 4. Which of the following does not hold true for the AllPermission class? a. The AllPermission class disables security restrictions by granting all the permissions. b. The AllPermission class can be used with almost all applets and applications. c. The AllPermission class makes the security file lighter.
THE ACCESS CONTROLLER AND PERMISSIONS IN JAVA 2
Chapter 7
d. The AllPermission class is used by system administrators because they perform multiple tasks, most of which require permissions. 5. Which class is used as the base class for permissions that have only names and no action list? a. java.security.AllPermission b. java.security.Permission c. java.security.BasicPermission d. java.security.SecurityPermission 6. Which of the following statement(s) hold true while creating custom permission types? a. You extend the Permission or BasicPermission class. b. You extend the SecurityPermission class. c. You implement the implies(), equals(), or hashCode() methods. d. You implement the run() method.
Short Questions 1. What do permission implications mean? 2. Discuss the java.security.UnresolvedPermission class. 3. Write the code for a custom permission type, MyTrialPermission.
Answers Multiple Choice Answers 1. b. If the caller is marked as privileged, the checkPermission() method stops checking the code and returns. 2. c. The caller is marked as privileged by calling the doPrivileged() method without the context argument. 3. c and d. Most of the permissions are derived from the abstract class, java.security.Permission. In addition to this class, there is an abstract class java.security.PermissionCollection and a final class java.security.Permissions. An instance of the PermissionCollection class holds permissions of the same type. It is used to store Permission objects of a single category such as Net permissions. On the other hand, the Permissions class is a collection of PermissionCollection objects.
167
168
Part II
ADVANCED JAVA SECURITY CONCEPTS
4. b. The AllPermission class disables security restrictions by granting all the permissions. Therefore, this class should be used sparingly. Security considerations should be kept in mind before granting this permission. This permission should be granted only to trusted applets or applications where adding all permissions would make the security policy file bulky. 5. c. The java.security.BasicPermission class is used as the base class for permissions that have only names and no action list. 6. a and c. While creating custom permission types, you generally extend the Permission or BasicPermission class. Then, you need to concentrate on implementing the implies(), equals(), or hashCode() methods of these classes. Finally, the new class is included in the application package.
Short Answers 1. All the subclasses created from the Permission class must implement the implies() method, which is used to compare permissions. For example, consider the following code statement: permission p1 implies permission p2
This statement indicates that if a program is granted permission p1, it is also granted permission p2. This method compares permissions and defines the relation of a particular permission class with remaining permission classes. There are layers of implication, which are not always direct. For example, consider an application that has been assigned the permission to write to the entire file system. This implies that the specified application has access to all Java system files including the Java runtime environment. Therefore, you do not need to assign a specified PropertyPermission for this. The specified application has indirectly been granted full permissions. There are various examples, which further implicate the permissions granted to a specified applet or application. Therefore, a particular permission simultaneously grants access to resources associated with it. For example, if you assign the runtime permission to modify threads, you also assign the remaining permissions associated with this action. Other dangerous permissions include those that allow you to set system properties or those that allow you to specify runtime permissions for specifying packages. AllPermission is the most dangerous of them all. Therefore, before assigning permissions to applets or applications, you need to consider the serious implication behind doing so. 2. When a policy is initialized, there are certain permissions whose Permission class does not exist at this time. Such permissions are called unresolved permissions. This situation generally arises when the code that implements the
THE ACCESS CONTROLLER AND PERMISSIONS IN JAVA 2
Chapter 7
specified permission class is not loaded in the Java environment. Therefore, the UnresolvedPermission class stores information about these unresolved permissions in the policy file. When the checkPermission() method is called in the case of an unresolved permission, it is checked to determine whether the associated class has been loaded. If the class has been loaded, the permissions of the specified type are resolved. In other words, a new object of the loaded type is instantiated with the information contained in the UnresolvedPermission class and this object replaces the UnresolvedPermission instance. If the UnresolvedPermission instance cannot be resolved, the permission is considered invalid. 3. The code for the custom permission type, MyTrialPermission, is given here: import java.security.*; import java.io.*; public class MyTrialPermission extends BasicPermission { public MyTrialPermission (String name) { super(name); System.out.println(“The MyTrialPermission constructor (String name) is called”); } public MyTrialPermission (String name, String actions) { super(name); System.out.println(“The MyTrialPermission constructor (String name, String actions) is called”); } }
169
This page intentionally left blank
Chapter 8 Security Configuration Files and Security APIs
T
he security features in Java have changed across various versions. As you have read in the preceding chapters, there have been many changes in the security model in Java 2. The security file, java.security, and the policy file, java.policy, are the primary security configuration files of JVM. There have been changes in these files also. These files are stored in the java.home/lib/security directory after the installation of the Java 2 SDK. You can update or rewrite these files after the installation. These configuration files are used to define the security policies and access permissions. There is another security file that is stored in this directory, the cacerts file. Java 2 also includes certain security packages that help you build secure applications. I’ll discuss these security files and the security APIs in this chapter.
The Security Properties File This file is used to set the various security properties that are used by the classes in the java.security package. The listing of this file is given here: # # This is the “master security properties file”. # # In this file, various security properties are set for use by # java.security classes. This is where users can statically register # Cryptography Package Providers (“providers” for short). The term # “provider” refers to a package or set of packages that supply a # concrete implementation of a subset of the cryptography aspects of # the Java Security API. A provider may, for example, implement one or # more digital signature algorithms or message digest algorithms. # # Each provider must implement a subclass of the Provider class. # To register a provider in this master security properties file, # specify the Provider subclass name and priority in the format # #
security.provider.=
# # This declares a provider, and specifies its preference
SECURITY CONFIGURATION FILES AND SECURITY APIS
Chapter 8
# order n. The preference order is the order in which providers are # searched for requested algorithms (when no specific provider is # requested). The order is 1-based; 1 is the most preferred, followed # by 2, and so on. # # must specify the subclass of the Provider class whose # constructor sets the values of various properties that are required # for the Java Security API to look up the algorithms or other # facilities implemented by the provider. # # There must be at least one provider specification in java.security. # There is a default provider that comes standard with the JDK. It # is called the “SUN” provider, and its Provider subclass # named Sun appears in the sun.security.provider package. Thus, the # “SUN” provider is registered via the following: # #
security.provider.1=sun.security.provider.Sun
# # (The number 1 is used for the default provider.) # # Note: Statically registered Provider subclasses are instantiated # when the system is initialized. Providers can be dynamically # registered instead by calls to either the addProvider or # insertProviderAt method in the Security class.
# # List of providers and their preference orders (see above): # security.provider.1=sun.security.provider.Sun security.provider.2=com.sun.rsajca.Provider
# # Class to instantiate as the system Policy. This is the name of the class # that will be used as the Policy object. # policy.provider=sun.security.provider.PolicyFile
# The default is to have a single system-wide policy file,
173
174
Part II
ADVANCED JAVA SECURITY CONCEPTS
# and a policy file in the user’s home directory. policy.url.1=file:${java.home}/lib/security/java.policy policy.url.2=file:${user.home}/.java.policy
# whether or not we expand properties in the policy file # if this is set to false, properties (${...}) will not be expanded in policy # files. policy.expandProperties=true
# whether or not we allow an extra policy to be passed on the command line # with -Djava.security.policy=somefile. Comment out this line to disable # this feature. policy.allowSystemProperty=true
# whether or not we look into the IdentityScope for trusted Identities # when encountering a 1.1 signed JAR file. If the identity is found # and is trusted, we grant it AllPermission. policy.ignoreIdentityScope=false
# # Default keystore type. # keystore.type=jks
# # Class to instantiate as the system scope: # system.scope=sun.security.provider.IdentityDatabase
# # List of comma-separated packages that start with or equal this string # will cause a security exception to be thrown when # passed to checkPackageAccess unless the # corresponding RuntimePermission (“accessClassInPackage.”+package) has # been granted. package.access=sun.
SECURITY CONFIGURATION FILES AND SECURITY APIS
Chapter 8
# # List of comma-separated packages that start with or equal this string # will cause a security exception to be thrown when # passed to checkPackageDefinition unless the # corresponding RuntimePermission (“defineClassInPackage.”+package) has # been granted. # # by default, no packages are restricted for definition, and none of # the class loaders supplied with the JDK call checkPackageDefinition. # #package.definition=
The preceding code listing has many comments and very few entries in the security properties file. Let’s observe these entries without the comments that actually explain them. security.provider.1=sun.security.provider.Sun security.provider.2=com.sun.rsajca.Provider policy.provider=sun.security.provider.PolicyFile policy.url.1=file:${java.home}/lib/security/java.policy policy.url.2=file:${user.home}/.java.policy policy.expandProperties=true policy.allowSystemProperty=true policy.ignoreIdentityScope=false keystore.type=jks system.scope=sun.security.provider.IdentityDatabase package.access=sun. #package.definition=
I’ll now explain the default entries in the security properties file in detail.
Security Providers Observe the following entry: security.provider.1=sun.security.provider.Sun
The preceding entry defines the cryptographic package providers along with their locations and preference orders. These providers offer a cryptographic implementa-
175
176
Part II
ADVANCED JAVA SECURITY CONCEPTS
tion to the Java security API. There must be at least one security provider specification in the security properties file. If another provider needs to be defined, it should be specified as: security.provider.=
The preceding statement adds the provider with preference order n. The providers are ordered starting from 1 to n. In such a multi-provider implementation, the provider with higher preference is chosen. The security engine looks for the desired implementation in the list of providers starting from the first one in the list. If the given provider matches its requirements, it does not search further. However, it continues to search through the provider list until the desired implementation is met. The keyword in the preceding code statement defines the subclass of the Provider class. The constructor of the Provider class sets the values of the different properties that are required by the Java security API to locate the facilities implemented by providers. The Provider subclasses are instantiated when the system is initialized. SUN is the default provider with Java 2. Its Provider subclass is named Sun. Both of these are part of the sun.security.provider package. The SUN provider offers the following services: ◆ The JKS keystore implementation for the proprietary keystore type ◆ An implementation of the Digital Signature Algorithm (DSA) ◆ A DSA key pair generator for generating key pairs for the DSA algorithm ◆ A DSA algorithm parameter generator ◆ A DSA algorithm parameter manager ◆ A DSA key factory ◆ A certificate factory for X.509 certificates and Certificate Revocation Lists
(CRLs) You’ll read more about security provider implementation in Chapter 12, “Introduction to Cryptography.”
Policy Providers The policy.provider entry in the security properties file specifies the file that will be used to instantiate the system policy. This entry defines the class name to be used as the Policy object that defines the permissions available for the specified code source. The code source contains the URL location of the code and the certificates of the entities that have signed the code.
SECURITY CONFIGURATION FILES AND SECURITY APIS
Chapter 8
The default value contained in this entry is sun.security.provider.PolicyFile. The PolicyFile class of the sun.security.provider package defines the Java 2 SDK policy implementation.
Policy File URL Locations The URL locations of policy files are defined in the security properties file through the value of properties that are defined as: policy.url.n=URL
In the preceding code statement, n stands for the precedence number of the policy file to be considered. URL points to the path of the policy file associated with the precedence number. By default, there are two URL locations for policy files, namely a system-wide policy file in the location file:/${java.home}/lib/security/java.policy and the user-defined policy file in the location file:${user.home}/.java.policy. Other than these two URL locations, you can also specify an ordered list of URLs for the policy files to load. The order of these policy files is 1-based. This indicates that if there are policy files starting from 1 to n, the policy.url.1 file is read first. The precedence numbers must be in serial order and continuous. If they are not so, the policy files that break the serial order are ignored. For example, consider the policy files: policy.url.1, policy.url.2, and policy.url.4. In this case, only the files, policy.url.1 and policy.url.2, are considered and the policy.url.4 file is ignored. Therefore, when the Policy class is initialized, the system policy is added to it followed by the user policy. You can also add customized policy files, as specified by the path given here: policy.url.3=file:/C:/niit/niitpolicy
Policy files need not be confined to the local system. They can also be stored on remote systems and then be accessed by specifying the URL path. Therefore, system administrators install a system-wide policy file on a policy server, which can be accessed by the client computers that also store their local user-defined policy files.
Policy File Property Expansion This entry in the security properties files determines whether property expansion should be allowed in policy files. The syntax of the property expansion entry is specified here: policy.expandProperties=boolean
177
178
Part II
ADVANCED JAVA SECURITY CONCEPTS
If the preceding security property is set to true, it indicates that the system property variables, such as ${java.home} and ${user.home}, are translated into their respective values every time they appear in a policy file. On the other hand, if this property is set to false, the system property variables are not expanded. They have to be hardcoded. For example, in the case of ${java.home}, you would have to type the path as C:\jdk1.3.0_02\jre. When this property is set to true, the policy files are portable across different platforms. This indicates that the same policy file can be used across different operating systems. This enables system administrators to store the policy file on the policy server from where it can be accessed by multiple local clients. It is always a good practice to have expandable system properties in policy files. For example, the separators used in different operating systems are automatically considered when the property is being expanded. On the other hand, if the property expansion is set to false, you have to change the separator manually depending on the operating system you want to use. A backslash (\) is used in the case of Windows systems, whereas a forward slash (/) is used in Unix systems. Therefore, the policy files in which this property is set to true are more generic and can be changed specifically depending upon the differences preventing portability.
Specifying an Additional Policy File There may be situations when you need to specify a custom policy file for a given Java application. However, in such a case, you need to set a specific property in the security properties file to true. The syntax of this property is given here: policy.allowSystemProperty=boolean
If the value of this security property is set to true, you can specify an additional policy file at the command-line prompt by using the code statement given here: java -Djava.security.manager -Djava.security.policy=niitpolicy MyApplication
The preceding code statement denotes that an additional policy file, niitpolicy, will be used along with the policy files already specified in the security properties file during the execution of the MyApplication Java application. When a double equals sign (==) is specified instead of a single equals sign (=), it indicates that only the additional policy file will be used with MyApplication. Consider the code statement given here: java -Djava.security.manager -Djava.security.policy==niitpolicy MyApplication
The same holds true for applets on using the appletviewer command. However, the command should be preceded by the -J flag, as specified here: appletviewer -J -Djava.security.policy==niitpolicy MyApplet.html
SECURITY CONFIGURATION FILES AND SECURITY APIS
Chapter 8
Because the double equals sign (==) is specified in the preceding code statement, it indicates that the niitpolicy file will be used exclusively with MyApplet. Therefore, setting allowSystemProperty to true provides users with freedom to create their own policy file, which caters to their applications and applets. On the other hand, if allowSystemProperty is set to false, you will not be able to use custom policy files with the Java applications or applets. System administrators might set this property to false, thereby restricting users from defining their own security policies that override the system-wide policy file.
Policy Identity Scope Whenever a JAR file is encountered, the identity scope of all the trusted entities needs to be determined. The property in the security property file that allows you to do this is given here: policy.ignoreIdentityScope=boolean
If the preceding property is set to true, the identity scope for the trusted entities is ignored and the Java 2 security policy is enforced. If it is set to false, the identity scope for trusted identities is looked into. If the trusted identity is found, it is granted AllPermission.
The Keystore Type The following property of the security properties file specifies the keystore type to be used: keystore.type=jks
By default, the keystore type property is set to Java Keystore (jks), which is the proprietary keystore type of SUN Microsystems. The jks format protects the integrity of the keystore with a keystore password. The keystore can be protected from alteration through the hash value of the entire keystore. A private key in the keystore is protected by a different password. This password can also be identical to the keystore password. The private keys are generally stored in an encrypted format.
System Identity Scope The system identity scope manages a collection of certificates, keys, and trust levels. This collection is, in turn, used by applications for authentication and signing. This identity scope is specified by the system.scope property in the security properties file.
179
180
Part II
ADVANCED JAVA SECURITY CONCEPTS
The syntax of the property is given here: system.scope=sun.security.provider.IdentityDatabase
By default, the value is set to the sun.security.provider.IdentityDatabase class. An instance of this class is created when any application or applet is executed. The system identity scope can be instantiated to any class by using the following code statement: system.scope=className
Checking Package Access Access to a package is restricted by the checkPackageAccess() method of the SecurityManager class. However, how does this method judge the packages to be restricted? This can be done by the following entry in the security properties file: package.access=string1, string2, …, stringn
The comma-separated strings represent package names that throw security exceptions when passed to the checkPackageAccess() method. However, an exception is not raised, if the associated RuntimePermission is granted. The default entry in the security properties file is as follows: package.access=sun.
The checkPackageAccess() method takes a string argument that represents a package name and throws an exception if the calling thread is not given access to the specified package. This method is used when the class loaders load classes. If the access to a restricted package is checked, the checkPermission() method is called with the permission specified here: java.lang.RuntimePermission(“accessClassInPackage.packageName”)
If RuntimePermission has not been specified, access to the package is denied and a security exception is thrown.
Checking the Package Definition By default, no packages are restricted for definition; in other words, a thread can define classes in any package. The package definition is checked by the package.definition entry in the security properties file. This entry is not set to any value and is commented out, indicating that no packages are restricted for definition. #package.definition=
SECURITY CONFIGURATION FILES AND SECURITY APIS
Chapter 8
However, you can specify a comma-separated list of packages for this entry. These packages are passed to the SecurityManager.checkPackageDefinition() method, which throws SecurityException if the associated RuntimePermission is not specified. The checkPackageDefinition() method is called when the calling thread tries to define classes in the package specified in the argument to the method. For restricted packages, the checkpermission() method is called with the following permission argument: java.lang.RuntimePermission(“defineClassInPackage.packageName”)
The checkPackageDefinition() method is used by the loadClass() method of class loaders. However, none of the class loaders specified with the Java 2 SDK call this method because no package is restricted for definition. Let’s discuss the next security-related file, the security policy file, in the subsequent section.
The Security Policy File A Policy object represents the security policy for a Java application environment. The security policy determines the permissions available for code from different sources. In a default policy implementation, the security policy can be specified in various configuration files. You are already aware that policy files can be created by using either text editors or the policy tool. I’ve already mentioned that there are two default policy files, the system-wide policy file and a user policy file. The policy configuration files specify the permissions assigned to code from various sources. For example, any applet or application needs permissions to perform secured actions. The configuration files contain various entries, such as the keystore and grant entries, for assigning permissions to code from various sources. I’ll discuss these entries in detail in the subsequent sections.
Keystore Entries You can refer to a keystore in a policy configuration file to search for the public keys of the signers specified in the grant entries of the policy files. A keystore is a database of private keys and digital certificates that authenticate the associated public keys. If there are grant entries that specify signer aliases, a subsequent keystore entry must appear as well in the policy configuration files. There can be only a single keystore entry in a policy file. However, if multiple keystore entries are defined, only the first one is considered and the rest are ignored. The keystore entry has the following syntax: keystore “keystore_URL”, “keystore_type”;
181
182
Part II
ADVANCED JAVA SECURITY CONCEPTS
In the preceding code statement, keystore_URL specifies the URL location of the keystore and keystore_type specifies the keystore type. If the keystore type is not specified, the type value is captured from the keystore.type property in the security properties file. The keystore URL is relative to the location where the policy file is stored. For example, consider the following URL location of the policy file, as specified in the security properties file: policy.url.3=http://www.mysite.com/policy/my.policy
Consider that the my.policy file contains the following entry: keystore “.keystore”;
In the preceding case, the keystore will be loaded from http://www.mysite.com/policy/.keystore. As seen in the preceding example, you can load a keystore from a remote location because you specified the keystore location as a URL.
Grant Entries Before executing code, it is a good practice to determine whether the code is trusted. Code always comes from a specified code source that is represented by the CodeSource object. The code source includes the URL location from where the code was downloaded and a reference to the certificates containing the public keys associated with the private keys, which are used to sign the code. The certificates are referred to by alias names from the user’s keystore. Grant entries are used to grant permissions to the codes originating from various sources. Each grant entry contains various permission entries preceded by the optional codeBase and signedBy clauses. These clauses specify the details for the code that needs to be granted permission. The syntax of a grant entry is as follows: grant signedBy “signer_names”, codeBase “URL” { permission permission_class_name “target_name”, “action”, signedBy “signer_names”; .... permission permission_class_name “target_name”, “action”, signedBy “signer_names”; };
As seen in the preceding code statements, the grant entry begins with the grant keyword and is followed by the codeBase and signedBy name-value pairs. Let’s discuss the codeBase and signedBy entries in detail in the subsequent sections.
SECURITY CONFIGURATION FILES AND SECURITY APIS
Chapter 8
The signedBy Entry The signedBy entry is optional and can appear in any order. This entry indicates an alias name for a certificate stored in the keystore. The public key of this certificate is used to verify the digital signature on the code. Permissions are granted to the code that is signed by the private key associated with the public key in the keystore entry identified by the alias name. The signedBy entry contains a comma-separated list of multiple alias names. When multiple signers are specified, the code needs to be signed by each of them. Therefore, there is an AND relation between the multiple signers. If it were an OR relation, you would need to specify the same grant entries with the different signers multiple times. For example, consider that the signedBy clause contains “Eric, Sam, Spencer”. This indicates that the specified code is signed by Eric, Sam, and Spencer. If this entry is missing, it indicates that the permissions in the specified grant entry are valid for code that is not signed or for code that is signed by anyone.
The codeBase Entry The codeBase entry is also optional, and it specifies the location of the code source. Therefore, you grant permissions to code originating from the specified location. The codeBase value is a URL location. The meaning of the value in this entry depends on the trailing characters in the specified entry. Consider the following codeBase entry: codeBase “file:/C:/MyDocs/”
The preceding entry with a trailing / indicates that the specified permissions are granted to all the class files (and no JAR files) in the MyDocs directory. Consider the following codeBase entry: codeBase “file:/C:/MyDocs/*”
The preceding entry with a trailing /* indicates that the specified permissions are granted to all the files, both class files and JAR files, in the MyDocs directory. Consider the following codeBase entry: codeBase “file:/C:/MyDocs/-”
The preceding entry with a trailing /- indicates that the specified permissions are granted to all the files, both class files and JAR files, in the MyDocs directory and in all its sub-directories recursively.
183
184
Part II
ADVANCED JAVA SECURITY CONCEPTS
Consider the following codeBase entry: codeBase “file:/C:/MyDocs/MyClasses.jar”
The preceding entry grants the specified permissions to only the MyClasses.jar file in the specified directory.
The permission Entries The permission entries begin with the permission keyword and specify a permission that is granted to the given code source. The syntax of a permission entry is: permission permission_class_name “target_name”, “action”, signedBy “signer_names”;
The permission_class_name keyword signifies a specific permission type, such as java.io.FilePermission or java.net.SocketPermission. The target_name field defines the target for the specified permission. For example, the file name for FilePermission is the target for the permission. The action field is not required for all types of permissions. This field is required for FilePermission; however, it is not required for RuntimePermission. For example, FilePermission has the associated actions: read, write, execute, and delete. The signedBy clause in the permission entry is optional. It indicates a signed permission. Therefore, the permission class must be signed by the entities specified after the signedBy clause to be granted the permissions. This clause is necessary for the permission classes that are not a part of the Java API and have been downloaded from a remote site. Therefore, when the remote permission class is signed by a trusted identity, it indicates that the code has been checked for authenticity. You are already aware that multiple signers can sign applets and applications and these signers are related by the AND relation. However, the permission classes can be signed by only a single entity. If multiple signers are present, only the first one is considered. Consider the following code statement: grant{ permission java.io.FilePermission “<>”, “write”, signed by “Duke”;};
The preceding FilePermission will be granted only if the specified Permission class is signed by the private key in the certificate specified by the Duke alias.
SECURITY CONFIGURATION FILES AND SECURITY APIS
Chapter 8
NOTE On the Windows operating system, when you specify a file path in a string, you need to include two backslashes instead of a single one. However, this does not hold true if the file path is in a codeBase URL. The reason for this is that the java.io.Stream.Tokenizer class uses a \ as an escape string and, therefore, two backslashes are required to denote a single backslash. The tokenizer converts the double backslash into a single one when deriving the result.
Now, let’s consider certain sample entries in a policy file. Consider the following code statement: grant signedBy “Marc” { permission java.io.FilePermission “/MyDocs/MyFile.txt”, “read”; };
The preceding grant entry indicates that the read access is granted to MyFile.txt only if the code is signed by Marc. If the signedBy clause is missing, it indicates that everyone can be granted the specified permission. Consider another sample entry in the policy file: grant signedBy “Marc”, codeBase “file:/home/MyDocs/*” { permission java.security.SecurityPermission “security.insertProvider.*”; };
The preceding entry specifies that if the code is loaded from a signed JAR file that is located in the home/MyDocs directory on the local file system and the public key is referenced by the alias name “Marc” in the keystore, the code can add providers using the insertProvider() method. Now, I’ll discuss the last security file, cacerts.
The cacerts File The cacerts file is also called the Certificates KeyStore file. It represents a systemwide keystore with Certification Authority (CA) certificates. The cacerts file can be
185
186
Part II
ADVANCED JAVA SECURITY CONCEPTS
managed and modified by system administrators by using the keytool command. However, the keystore type must be the default JKS format. The cacerts file is located in the java.home\lib\security\ directory. It contains five VeriSign root CA certificates. These certificates can be listed by using the keytool command, as given here: keytool -list -keystore cacerts
When you execute the preceding command, you will be prompted for a password. The default password is changeit. You should change this password because the keystore contains trusted CA certificates that should not be tampered with.
Java 2 Security APIs In addition to the files discussed in the preceding sections, Java 2 also provides certain security packages that help in building secure Java applications. Java 2 provides two new subpackages, java.security.cert and java.security.spec, in addition to the package, java.security, and its subpackages, java.security.acl and java.security.interfaces, which were there in jdk1.1. Java 2 introduced two new subpackages, java.security.cert and java.security.spec. These packages form the Java Cryptographic Architecture ( JCA). JCA provides Java programs with cryptographic capabilities. Let’s discuss these packages in detail in the subsequent sections.
The java.security Package This security package includes all the classes and interfaces needed by the security framework in Java 2. Most of the classes and interfaces supported by this package are abstract and provider-based. The classes included in this package mainly represent keys, keystores, signatures, access control, and permissions. Let’s discuss the components of this package in detail in the subsequent sections.
The GuardedObject Class and Guard Interface An object created by the Guard interface is used to guard a protected resource. Consider a scenario where the supplier of a resource is not in the same thread as the consumer of that particular resource. In this scenario, the consumer thread cannot provide the access control context information to the supplier thread due to security reasons. Therefore, in Java 2, the GuardedObject class protects access to the specified resource.
SECURITY CONFIGURATION FILES AND SECURITY APIS
Chapter 8
First, I’ll explain how the supplier of a resource uses the GuardedObject class to provide information to the consumer of a resource. The supplier of a resource creates an object that represents the resource, and then it creates GuardedObject that encapsulates this resource object, and finally provides GuardedObject to the consumer of the specified resource. While creating GuardedObject, the supplier also creates a Guard object and specifies certain security checks within it. Therefore, the consumer or anyone can obtain the resource object only if these security checks are satisfied. Guard is an interface, and objects can be created based on this interface. This interface contains a single method, checkGuard(). This method takes an Object parameter and performs certain security checks. Let’s consider an example to explain the use of the GuardedObject class and Guard interface. Consider that a thread is requested for read access to MyFile.txt. However, the thread has no knowledge of the requestor. Therefore, access control checking can be delayed by using the following code statements: FileInputStream fis = new FileInputStream(“MyFile.txt”); FilePermission perm = new FilePermission(“MyFile.txt”, “read”); GuardedObject go = new GuardedObject(fis, perm);
The thread passes the go object to the consumer thread (the requestor). For the requestor to obtain the access to the file, it needs to specify the following code statement: FileInputStream fis = (FileInputStream) go.getObject();
The getObject() method invokes the checkGuard() method on the Guard object, perm. This is so because the java.security.Permission class implements the Guard interface. Therefore, a proper access control check takes place when the requestor is granted the specified resource access. The implementation of the GuardedObject class and the Guard interface is very general. Therefore, developers can easily extend these classes and interfaces to create the appropriate access control tools.
The Provider Class This class is also a part of the java.security package. The phrase, cryptographic service provider, is used interchangeably with the term, provider. Providers refer to a set of packages that provide an implementation of cryptographic aspects in the Java 2 SDK. The Provider class is an interface for such packages. This class contains methods that provide access to the provider name, the version number, and additional information about providers.
187
188
Part II
ADVANCED JAVA SECURITY CONCEPTS
To provide implementation of cryptographic services, developers write the implementation code and create a subclass of the Provider class. The Java 2 security API needs to know the various services implemented by a provider. The subclass constructor sets the values of various properties that are used by the Java 2 security API to solve its purpose. Various types of services are provided by several provider packages. However, these services can differ in their implementation and features. Certain implementations may be software-based, whereas others may be hardware-based. They can also differ in their platform-independence. The Java Cryptography Architecture ( JCA) allows both users and developers to decide on cryptographic implementations depending upon their requirements. For each engine class in the Java API, a particular implementation is requested and instantiated. This is done by calling the getInstance() method on the engine class and passing to the method the name of the appropriate algorithm and the name of the provider whose implementation is needed. In the case where no provider is specified, the getInstance() method searches among the registered providers to locate an implementation of the requested service specified by the named algorithm. In Java, providers are installed by order of preference. This order matches the order in which these providers are searched for when no provider is specified. The provider argument is specified in the getInstance() method for developers who wish to specify the provider from which they want an algorithm. The getProviders() method of the Security class provides a list of all the installed providers. Therefore, a program can use this method to choose a provider from a given list. Let’s observe the process of installing providers.
Installing Providers For installing a provider, there are two phases, namely installing the provider package classes and configuring the provider. In turn, there are several ways of installing the provider package classes. These are listed here: ◆ You can install a provider class package by placing the ZIP file or the JAR file containing the class files anywhere on the CLASSPATH. ◆ You can install your provider JAR file as an installed or bundled extension in
the extensions directory.
SECURITY CONFIGURATION FILES AND SECURITY APIS
Chapter 8
Once you have installed the provider classes, you need to configure the provider by adding it to your list of approved providers. You can do this by editing the java.security file in the java.home/lib/security directory. Consider the code statement given here: security.provider.n=masterClassName
The preceding code statement specifies a type of property that you can set in the java.security file. This statement declares a provider and sets its precedence order as n. The provider’s master class is specified by masterClassName. This class is specified in the provider’s documentation. The master class is a subclass of the Provider class. You can also dynamically register providers by calling the addProvider() or insertProviderAt() method of the Security class. However, this type of dynamic registration is possible only by trusted programs.
NOTE The addProvider() method adds a provider at the end of the list of installed providers. On the other hand, the insertProviderAt() method adds a new provider at a specified position in the array of providers. As a result of this, all the providers after the inserted provider are shifted up one position. Both these methods return the preference position in which the provider was added to the list. A provider cannot be added if it is already installed. Therefore, in such cases, a –1 preference value is returned by these methods, indicating that the specified provider is already there in the list.
To change the preference position of a provider, you first need to remove the provider from the list and then add it again at the new position. To remove a provider, you need to call the removeProvider() method, which is defined here: public void removeProvider(String name)
The preceding method removes the provider specified by the string name. If the specified provider is not installed, the method returns silently without raising any exception. When the specified provider is removed, the providers following it are shifted down one position towards the starting of the list of installed providers.
The Security Class The Security class manages the installed providers and security-wide properties. This class is never instantiated and contains only static methods. Note that the meth-
189
190
Part II
ADVANCED JAVA SECURITY CONCEPTS
ods for adding or removing providers and those for setting the security properties can be invoked only by trusted programs. However, for code to be designated as trusted, it needs to be granted permissions for a particular action. Therefore, the permission required for adding a provider or inserting it at a specified position in the list is as follows: permission java.security.SecurityPermission “insertProvider.name”;
For removing a provider, the permission needed in the policy file is as follows: permission java.security.SecurityPermission “removeProvider.name”;
The Security class performs the following functions: ◆ Manages providers ◆ Sets the security properties
The various methods defined in the Security class for performing the preceding functions are listed here: ◆ getProviders() ◆ getProvider() ◆ addProvider() ◆ insertProviderAt() ◆ removeProvider() ◆ getProperty() ◆ setProperty()
A few of the preceding methods have already been discussed in a preceding section. I’ll discuss the remaining ones in the subsequent sections.
The getProviders() and getProvider() Methods These methods are used to query which providers are already installed. The syntax of the getProviders() method is as specified here: public Provider[] getProviders()
This method returns an array containing all the installed providers. The providers are ordered as per their preference order in the array. The syntax of the getProvider() method is as specified here: public Provider getProvider(String providerName)
SECURITY CONFIGURATION FILES AND SECURITY APIS
Chapter 8
This method returns the provider identified by providerName. The method returns null if the specified provider is not found.
The getProperty() and setProperty() Methods You are already aware that the Security class contains a list of system-wide security properties. These properties can be set and accessed by the setProperty() and getProperty() methods respectively. However, only the trusted programs can invoke these methods. The syntax of these methods is given here: public static String getProperty(String key) public static void setProperty(String key, String datum)
Keystore Management The java.security package provides interfaces and classes for key generation and management. In this section, I’ll discuss these classes and their use. The java.security package contains classes such as KeyFactory and KeyFactorySpi. The KeyFactory class is used to convert keys into key specifications. The reverse also holds true. The KeyFactorySpi class defines the Service Provider Interface (SPI) for the KeyFactory class.
NOTE Key factories allow you to create key objects from the given key specifications. On the other hand, they also allow you to derive key specifications from the present key objects. Therefore, key factories can be termed as bi-directional.
You can create a KeyFactory object by invoking the KeyFactory.getInstance() method. You can generate keys from a key specification by using the generatePublic() and generatePrivate() methods. To derive the key specification from a KeyFactory object, you can invoke the getKeySpec() method. The security package also provides a KeyPair class, which holds both the private and public keys. This class contains the getPrivate() and getPublic() methods, which return the private and public keys respectively. The KeyStore class contains interfaces to access and edit the information stored in a keystore. This class contains an in-memory collection of keys and certificates. The KeyStoreSpi defines the SPI for the KeyStore class. You can create an object of the
191
192
Part II
ADVANCED JAVA SECURITY CONCEPTS
KeyStore class by calling the getInstance() static method on the KeyStore class.
The syntax of this method is specified here: public static KeyStore getInstance(String type)
However, before using the KeyStore object, the actual keystore data needs to be loaded into the memory by calling the load() method. The definition of the load() method is specified here: public final void load(InputStream stream, char[] password)
The password passed to this method is optional, and it is used to check the integrity of the keystore data. Therefore, if this parameter is not provided, the keystore data is not checked for integrity. All the keystore entries are accessed with the help of unique aliases. The aliases() method returns a list of all the alias names in the keystore. The getKey() method returns the key associated with the given alias name. The deleteEntry() method deletes an entry associated with a given alias name. Finally, the inmemory keystore is saved by the store() method. The java.security package also contains a KeyPairGenerator class. This class is used to generate key pairs. The KeyPairGeneratorSpi class defines the SPI for this class. A KeyPairGenerator object is created by using the static getInstance() method of the KeyPairGenerator class. However, the object must be initialized before it can generate keys. There are four types of initialize() methods to perform this task. All of them have the same name, initialize, with a different signature. These initialize() methods allow you to generate key pairs either in an algorithmspecific or an algorithm-independent manner. These methods allow you to provide a source of randomness. They generally use the SecureRandom implementation of the highest-priority provider as this source.
The SignedObject Class This class is used for creating runtime objects whose integrity cannot be compromised. This class contains another Serializable object, the yet-to-be signed object and its signature. The signed object is a copy of the original object. After the copy is created, any modifications in the original object do not have any effect on the copy. Therefore, a signed object is permanent. Consider the following code statements: Signature sign = Signature.getInstance(algorithm,provider); SignedObject so = new SignedObject(myObject, signKey, sign);
SECURITY CONFIGURATION FILES AND SECURITY APIS
Chapter 8
NOTE The Signature class provides digital signature functionality to Java applications. The Signature object is used to generate and verify digital signatures. The signature algorithm object can be requested by providing either the algorithm name or both the algorithm and provider name. You’ll read more about the Signature class in Chapter 11, “Message Digests and Digital Signatures”.
The preceding statements create a signed object. Once you have received the signed object, you need to verify the same. You can do this by executing the following code statements: String algorithm = so.getAlgorithm(); Signature verifySign = Signature.getInstance(algorithm, provider); so.verify(verifySign);
There are many applications in which the SignedObject class can be used. It can be used by any Java application as an authorization token. This token can be passed around without being maliciously modified. This object can also be used to sign and serialize data for storage outside the Java runtime environment. Nested signed objects can be used to create a sequence of signatures that resemble a chain of authorization and delegation. The various permission classes discussed in a previous chapter are also part of the java.security package. The security package also provides APIs for message digests
and digital signatures. These will be discussed in Chapter 11, “Message Digests and Digital Signatures”.
The CodeSource Class This class specifies the code base that includes the URL location of the code and the certificates used to verify the signed code originating from this location. The code base is represented as a java.net.URL object and a list of signers, which is an array of java.security.cert.Certificate objects. Following is the constructor of this class: CodeSource(URL url, Certificate[] certs)
This class has the getLocation() method, which extracts the URL location, and the getCertificates() method, which locates the certificates. In addition to these
193
194
Part II
ADVANCED JAVA SECURITY CONCEPTS
methods, the CodeSource class also provides an implies() method, which returns whether the CodeSource object specified as an argument is implied by this CodeSource object.
The Principal Interface The java.security package includes this interface to represent the principals that denote entities, either independent users or companies. It is used to grant access to a specified resource. In Java 2, there is no implementation for this interface. A group of principals is denoted by the Group interface of the java.security.acl package. This package is discussed in a subsequent section.
The ProtectionDomain Class This class is used to represent a unit of protection within the Java application environment. The arguments to its constructor are a CodeSource object and a PermissionCollection object, which represent the set of permissions granted to the CodeSource object. ProtectionDomain(CodeSource codesource, PermissionCollection permissions)
This class contains the getCodeSource() method, which returns the code source of the domain. The getPermissions() method returns the permissions of the domain. It also contains an implies() method, which checks whether the specified protection domain implies the permission specified in the Permission object.
NOTE Classes that have the same permissions but are from different code sources belong to different protection domains. Each class belongs to only one protection domain. However, this depends on its code source and the permissions granted to the code source. All the classes from the same code source belong to the same protection domain.
The Policy Class The java.security package provides the Policy class, which represents the systemwide security policy for a Java application environment. This class specifies the permissions available for code from various sources. There is only a single Policy object
SECURITY CONFIGURATION FILES AND SECURITY APIS
Chapter 8
in effect, which is consulted by the ProtectionDomain class while initializing permissions. The policy information contained in the Policy object comes from the policy implementation. The getPolicy() method of this class returns the currently installed Policy object. This can be changed by invoking the setPolicy() method. The refresh() method reloads the current configuration of the Policy object. The getPermissions() method takes a CodeSource object as a parameter. This method evaluates the global policy and returns a Permissions object that contains the set of permissions allowed for the code from the specified code source.
The AlgorithmParameters Class This class is an engine class that provides an opaque representation of cryptographic parameters. This indicates that no direct access is allowed to the parameter fields. The only information derived is the name of the algorithm associated with the parameter set and the encoding for this parameter set. The getParameterSpec() method converts an AlgorithmParameters object to a transparent specification where you can access values from the parameter fields. The java.security package also includes the AlgorithmParameterGenerator class, which is used to generate a set of parameters to be used with a specific algorithm. The package also includes the AlgorithmParameterGeneratorSpi class, which defines the SPI for the AlgorithmParameterGenerator class.
The java.security.spec Package This package defines the classes and interfaces for key and algorithm parameter specifications. The key specifications are transparent representations of the key material that constitutes a key. This indicates that you can access the key material value through one of the get() methods defined in the associated specification class. This package contains the key specifications for the DSA public and private keys, RSA public and private keys, X.509 public and private keys in DER-encoded format, and PKCS#8 private keys in the DER-encoded format. This package also includes an algorithm parameter specification class, DSAParameterSpec, which specifies the set of parameters used with the DSA algorithm. This package provides two interfaces, AlgorithmParameterSpec and KeySpec. The AlgorithmParameterSpec interface is a specification of cryptographic parameters. The KeySpec interface is a specification of the key material that is contained in a cryptographic key. This interface also groups all the key specifications. Both these interfaces do not contain any methods or constants.
195
196
Part II
ADVANCED JAVA SECURITY CONCEPTS
The java.security.cert Package This package contains classes for managing digital certificates and certificate revocation lists (CRLs). It also provides classes for managing X.509 certificates and CRLs. The package contains the Certificate class, which is used to manage different types of identity certificates. The X509Certificate class extends the Certificate class and implements the X509Extension interface. This class is specifically for X.509 certificates. The CRL class is an abstraction of CRLs that have different formats with certain common uses. The CertificateFactory class generates certificates and CRL objects from their encodings. The CertificateFactorySpi class is used to define the SPI for the CertificateFactory class. CertificateFactory objects can be created by using the getInstance() method. The generateCRL() and generateCertificate() methods are used to create a CRL object and a certificate, respectively.
The java.security.interfaces Package This package is so named because it only contains interfaces that are used for generating the Digital Signature Algorithm (DSA) and Rivest, Shamir, and Adleman AsymmetricCipher Algorithm (RSA) keys. It generates the RSA keys as defined in the RSA Laboratory Technical Note PKCS#1 and the DSA keys as defined in NIST’s FIPS-186. DSAKey, DSAPrivateKey, and DSAPublicKey are interfaces to the DSA keys. This package also includes the DSAKeyPairGenerator interface for generating DSA key pairs. The DSAParams interface defines a DSA key family. The RSAKey, RSAPrivateKey, and RSAPublicKey interfaces are meant for RSA keys. The RSAPrivateCrtKey interface is an interface for private RSA keys. This interface follows the PKCS#1 standard and uses the Chinese Remainder Theorem (CRT) information values.
The java.security.acl Package This package includes interfaces for managing access control lists (ACLs). The classes and interfaces in this package have been superseded by the classes in the java.security package. The interfaces included in this package are as follows. The Acl interface represents an ACL. The AclEntry interface is used for denoting a single entry in an ACL. The Group interface is used to represent a group of principals. The Owner interface is used to manage the owners of ACLs or ACL configurations. Finally, the Permission interface represents a permission used to grant access to a specified resource.
SECURITY CONFIGURATION FILES AND SECURITY APIS
Chapter 8
Summary In this chapter, I discussed the security files in Java 2. These are the java.security, security policy, and cacerts files. I explained the entries in these files in detail. Then, I discussed the security packages in Java 2. These include the java.security, java.security.spec, java.security.cert, java.security.interfaces, and java.security.acl packages. Next, I talked about the java.security package extensively. Finally, I discussed the classes and interfaces contained in this package.
Check Your Understanding Multiple Choice Questions 1. There are two providers installed in a JVM, P1 and P2. P1 implements SHA1withDSA, SHA, and MD5. P2 implements SHA1withDSA, MD5withRSA, MD2withRSA, MD2, and MD5. P1 has preference order 1 and P2 has preference order 2. If you are looking for an MD5withRSA signature algorithm, which of the providers will be chosen? a. P1
c. P1 and P2
b. P2
d An error will occur
2. What does the following code statement denote? java -Djava.security.manager -Djava.security.policy= =ibmpolicy SecureApplication
a. The ibmpolicy file is used along with the other policy files specified while executing the Java application. b. Only the ibmpolicy file is used while executing the Java application. c. The ibmpolicy file is used along with the other policy files specified while executing the Java applet. d. Only the ibmpolicy file is used while executing the Java applet. 3. What does the following entry denote? codeBase “file:/C:/JavaDocs/*”
a. Permissions are granted to only the class files in the JavaDocs directory. b. Permissions are granted to all the files, both class and JAR files, in the JavaDocs directory.
197
198
Part II
ADVANCED JAVA SECURITY CONCEPTS
c. Permissions are granted to all the files, both class and JAR files, in the JavaDocs directory and all its subdirectories, recursively. d. Permissions are granted to all files on the hard drive. 4. I need to change the preference position of a provider to an intermediate position in the list. Which of the following provider methods should I choose? a. addProvider() b. insertProviderAt() c. removeProvider() d. getProviders() 5. Which is the package in which the classes and interfaces have been superseded by the classes in the java.security package? a. java.security.spec b. java.security.cert c. java.security.interfaces d. java.security.acl
Short Questions 1. Explain the steps for installing providers. 2. When do you need the GuardedObject class and the Guard interface?
Answers Multiple Choice Answers 1. b. P1 is first searched for the desired implementation. No implementation is found, so P2 is searched. The implementation is found in P2; therefore, it is returned. 2. b. When a double equals sign (==) is specified instead of a single equals sign (=), it indicates that only the additional policy file will be used with the specified application. 3. b. A codeBase entry with a trailing /* indicates that the specified permissions are granted to all the files, both class and JAR files, in the given directory.
SECURITY CONFIGURATION FILES AND SECURITY APIS
Chapter 8
4. b and c. To change the preference position of a provider, you first need to remove the provider from the list and then add it at the new position. The removeProvider() method is used to remove the provider from the list, and the insertProviderAt() method is used to insert the provider at the specified position in the list. 5. d. The classes and interfaces in the java.security.acl package have been superseded by the classes in the java.security package.
Short Answers 1. For installing a provider, there are two phases—installing the provider package classes and configuring the provider. You can install a provider class package by placing the ZIP file or the JAR file containing the class files anywhere on the CLASSPATH. You can install your provider JAR file as an installed or bundled extension in the extensions directory. Once you have installed the provider classes, you need to configure the provider by adding the specified provider to your list of approved providers. You can do this by editing the java.security file in the java.home/lib/security directory. To add a provider statically, use the security.provider.n=masterClassName entry. You can also dynamically register providers by calling the addProvider() or insertProviderAt() method of the Security class. However, this type of dynamic registration is possible only by trusted programs. 2. When the supplier of a resource is not in the same thread as the consumer of that resource, the consumer thread cannot provide the access control context information to the supplier thread due to security reasons. Therefore, in Java 2, the GuardedObject class protects access to the specified resource. The supplier of a resource creates an object that represents the resource, and then it creates GuardedObject, which encapsulates this resource object, and finally it provides GuardedObject to the consumer of the specified resource. While creating GuardedObject, the supplier also creates a Guard object and specifies certain security checks within it. Therefore, the consumer or anyone can obtain the resource object only if these security checks are satisfied. Guard is an interface, and objects can be created based on this interface. This interface contains a single method, checkGuard(). This method takes an Object parameter and performs certain security checks.
199
This page intentionally left blank
Chapter 9 An Introduction to Java 2 Security Tools
I
n Chapter 2, “Java Security Model”, I discussed the four security-related tools supported by the new Java security model. These include keytool, the jar commandline tool, the jarsigner command-line tool, and the policy tool. I discussed that keytool is used for key and certificate management. The jar command-line tool is used to compress and archive Java class files into JAR files. The jarsigner tool is used to sign and verify the JAR files created by the jar tool. Finally, the policy tool is used for creating and managing policy files, which contain permissions for accessing system resources. In this chapter, I’ll discuss the functions of these tools in more detail.
The keytool Command The keytool command is basically used to manage keystores. Using keytool, you can create key pairs and sign certificates, export the signed code along with the certificates, and import certificates to verify the signatures attached with the code. You can also issue certificate signing requests (CSRs) for signing code. These requests are sent to the certification authorities (CAs) for signing.
NOTE Keystores have two types of entries, key entries and trusted certificate entries. Key entries contain sensitive cryptographic key information. This information is stored in a secure format to protect it from unauthorized access. Therefore, such a key entry typically stores a private key along with its associated certificate chain for the corresponding public key. The jarsigner and keytool commands operate on only private key entries with their associated certificate chains. On the other hand, the trusted certificate entries contain a single public key certificate belonging to a secondary entity. However, the keystore owner trusts that the public key in this certificate actually belongs to the person who signs this certificate.
The syntax for using the keytool command is as follows: keytool [command]
AN INTRODUCTION TO JAVA 2 SECURITY TOOLS
Chapter 9
When you enter this command at the command prompt, you get the output as shown here: C:\>keytool command keytool usage:
Therefore, as seen in the preceding output, there are many commands and options associated with keytool. Let’s discuss these options in detail.
The keytool Options The command keyword used with the keytool command can be replaced by a number of options. I’ll discuss the options in the sequence in which they appear on the screen. First, let’s discuss certain features common to all these options. All the options are preceded with a minus (-) sign. The options can be specified in any order. Brackets surrounding any option indicate that the user will be prompted for the values, if they are not provided at the command line. There are certain options that appear for many commands. These are listed here: ◆ The -v option indicates that the command will display a verbose or detailed
output.
AN INTRODUCTION TO JAVA 2 SECURITY TOOLS
Chapter 9
◆ The -storetype option specifies the type of keystore to be used in the specified command. The default keystore type is returned by the getDefaultType() method in the java.security.KeyStore class as indicated by the keystore.type property. ◆ The -storepass option specifies the password used to protect the keystore.
If this option is not specified at the command line, the user is prompted for it. This option is generally provided for all commands that access the keystore. ◆ The -keystore option specifies the location of the keystore. The default keystore location is the .keystore file stored in the user’s home directory.
The keyword in bold represents the options of the keytool command. This option is used to generate a CSR, which is in the PKCS#10 format and is sent to a CA.
NOTE PKCS stands for Public-Key Cryptography Standards. PKCS#10 format is the certification request syntax standard. This standard describes the syntax for a certification request, which consists of a public key, a distinguished name, and a set of attributes. All of these are signed by the entity requesting certification. The reason for including a set of attributes is to provide additional information about a given entity. For example, the postal address for an entity can be included. If electronic mail is not functioning, the signed certificate can be returned to the postal address.
The private key and the X.500 name associated with the alias of the certificate are used to generate this request in the specified format. The CA authenticates the requestor and returns a certificate or chain of certificates. This certificate is used to replace the self-signed certificate in the keystore. Private keys are protected by a password. Therefore, to access these keys, you need to specify the associated password. The user is prompted for the password in two cases, when the –keypass
205
206
Part II
ADVANCED JAVA SECURITY CONCEPTS
option is not provided and when the key password is different from the keystore password. The CSR is signed as per the algorithm specified by the sigalg option and stored in the csr_file. If the file is not specified, the CSR is sent to the standard output device.
This option deletes the entry identified by the alias keyword from the keystore. If an alias is not provided at the command line, the user is prompted for it.
This option accesses the keystore and reads the certificate specified by the alias option and stores it in the file, cert_file. If the –rfc option is specified, the certificate is provided in the Base 64 format; otherwise, it is provided in the binary encoding format. If alias points to a trusted certificate, the specified certificate is returned. Otherwise, alias points to a key entry in a certificate chain and the first certificate in the chain is returned. This certificate authenticates the public key of the entity addressed by alias.
This option generates a key pair that constitutes the public key and its associated private key. It wraps the public key into an X.509 v1 self-signed certificate and stores it as a single-element certificate chain. This chain and the private key associated with it are stored in a new keystore entry identified by the alias keyword.
AN INTRODUCTION TO JAVA 2 SECURITY TOOLS
Chapter 9
NOTE The X.509 standard outlines the information that needs to go into a certificate. It also describes the format of the data that goes into a certificate. All X.509 certificates contain the version, serial number, signature algorithm identifier, issuer name, validity period, subject name, and subject public key information in addition to the signature. There are three versions defined for these certificates namely, v1, v2, and v3.
The keyalg keyword identifies the algorithm used to generate the key pair. The keysize keyword specifies the size of the keys to be generated. The sigalg keyword specifies the algorithm to sign the self-signed certificate. The algorithm specified by the sigalg keyword should be compatible with that specified by the keyalg keyword. The dname keyword specifies the X.500 distinguished name to be related to the alias keyword. This name is used in the Issuer and Subject fields of the self-signed certificate. The valDays keyword specifies the validity period for which the specified certificate would be valid. The keypass keyword is used to specify the password for the private key in the key pair. If this keyword does not specify a password, the user is prompted for the password. The password should be at least six characters in length. If the user presses the Enter key instead of typing the password, the keystore password is used as the password for the primary key.
The -help Option This option provides help on the use of the keytool command. It lists all the commands and options supported by the command.
This option reads the JDK 1.1.x-style identity database represented by the file, idb_file. After reading, it adds its entries to the keystore. If a file is not specified with the command, the identity database is read from the standard input device. If the keystore does not exist, it is created when the command is executed. Only the database entries that are marked as trusted are imported to the keystore, and a keystore entry is created. The identity entry’s name is used as the alias name for the keystore entry. All the untrusted entries are ignored. The private keys from the database entries are encrypted by the password represented by the storepass keyword.
207
208
Part II
ADVANCED JAVA SECURITY CONCEPTS
This same password protects the keystore as well. However, you can assign different passwords to the private keys by using the keytool command. An identity entry in the identity database can have multiple certificates for the same public key. However, a keystore entry for a private key includes the key and a certificate chain where the first certificate of the chain is the public key associated with the specified private key.
This option reads the certificate or certificate chain and stores it in the keystore entry identified by the alias keyword. The certificate or certificate chain is provided from cert_file. If the file is not provided, the certificate is read from the standard input. The keytool command can import X.509 v1, v2, and v3 certificates, and the PKCS#7 format certificate chains consisting of such types of certificates. The data that is to be imported has to be provided in either Base 64 or binary encoding format.
NOTE The PKCS#7 format stands for the cryptographic message syntax standard. This standard describes the general syntax for data that has cryptography techniques applied to it. Examples of cryptography techniques are digital signatures and digital envelopes.
When you are importing a new trusted certificate, make sure that the alias does not already exist in the keystore. The keytool command verifies this certificate by attempting to add it to the certificate chain containing trusted certificates. If the trustcacerts option is specified, additional certificates are considered for constructing the chain of trust. These certificates are specified in the cacerts file. The cacerts file contains a system-wide keystore of CA certificates. This file is present in the java.home\lib\security directory. The cacerts file can be configured and managed by using keytool and specifying jks as the default keystore type. The default password provided to this file is changeit. This password should be changed along with the file’s default access permissions when you install JDK.
AN INTRODUCTION TO JAVA 2 SECURITY TOOLS
Chapter 9
If the keytool command fails to build a trust path from the certificate that needs to be imported up to a self-signed certificate, the certificate information is printed. The user needs to verify this information for validity before importing it as a trusted certificate. The user can validate this information by matching the displayed certificate fingerprints with the expected ones. It is confirmed that the original certificate has not been replaced during transit if the fingerprints match. To avoid marking a JAR file with malicious applets as trusted, it is important to verify the certificate before importing it. However, if the -noprompt option is specified, the user will not be prompted for verification. On the other hand, if a certificate reply is imported, it needs to be validated by using trusted certificates from the keystore or the cacerts file. However, keytool validates the reply differently, depending on whether the reply is a certificate or a certificate chain. If the reply is a single X.509 certificate, the keytool command tries to build a trust chain starting from this reply until a self-signed trusted certificate. Therefore, this certificate reply and the related certificates are used to form a new certificate chain with the specified alias. On the other hand, if the reply is a PKCS#7 certificate chain, the chain is arranged before the keytool command validates it. The chain is arranged starting with the user certificate and ending with the self-signed root CA certificate. The keytool command tries to match the root CA certificate with the trusted certificates available in the keystore or the cacerts file. If keytool is unable to find a match, the root CA certificate information is printed. The user can verify this information by matching the fingerprints. If the -noprompt option is provided, there is no interaction with the user. The new certificate chain thus formed replaces the old certificate chain for the specified entry. If a valid keypass is provided, the new chain can also replace the old chain. If the password is not provided and is different from the keystore, the user is prompted for it.
This option is used to create multiple certificate chains for a given key pair. This option can be used for backup purposes. As the name suggests, it is used to create a clone of an already existing keystore entry.
209
210
Part II
ADVANCED JAVA SECURITY CONCEPTS
This option creates a new keystore entry that has the same private key and certificate chain as the original entry. The original entry is marked by the alias keyword, whereas the new entry is identified by the dest_alias keyword. If the destination alias is not specified, the user is prompted for it. You are already aware that the private key is protected by a password, which is specified by the keypass keyword. Therefore, an entry is cloned only if the user provides the valid keypass. The user is prompted for this password only if the password is different from that of the keystore or keypass is not specified. Can I specify a different private key password for the new keystore entry? Yes, you can do this in two ways. One way is to specify the new_keypass value with the -new option. Another way is not to specify the -new option. In the latter case, the user is prompted to specify a password for the new entry. If the user does not enter any password, the password for the original entry is applied to the new entry as well.
This option is used to change the old password identified by the old_keypass keyword to the new password identified by the new_keypass keyword. The user is prompted for the password in two cases, when the -keypass and -new options are not specified and when the password for the private key is different from that of the keystore password.
This option is used to display the contents of the keystore entry marked with the alias name. In case the -alias option is not specified, the entire keystore entries are printed. The option displays the contents on the standard output device. As seen in the preceding code, either the -v or -rfc option needs to be provided at a time. Both these options cannot be specified together. Let’s first understand the difference between the two options. The -v option prints the certificate in human-readable format. Information such as the owner, issuer, and the serial number is also printed with this option. On the other hand, the -rfc option prints the certificate contents using the printable encoding format.
AN INTRODUCTION TO JAVA 2 SECURITY TOOLS
Chapter 9
The -printcert Option -printcert
[-v] [-file ]
This option is different from the -list option in that it can be used without a keystore specification. The option reads the certificate from the file specified by the cert_file keyword. If the -file option is not specified, the certificate is read from the standard input device. The certificate is displayed in human-readable format.
This option is used to generate a X.509 v1 self-signed certificate. It does this by using keystore information, namely the public and private keys associated with the specified alias name identified by the alias entry. If the -dname option is specified at the command line, dname is used as the distinguished name both for the issuer and subject of the certificate entry. The -validity option specifies the validity limit for the new certificate in the number of days. The new certificate is stored as a single-element certificate chain in the keystore entry identified by the alias name. Therefore, this new certificate replaces the existing certificate chain. The -sigalg <sigalg> option specifies the algorithm to be used to sign the newly generated certificate. The private key is protected by the password specified by the keypass keyword. If keypass is not specified and the private key password is different from that of the keystore password, the user is prompted for the password.
This option is used to specify a new password for the keystore identified by the -keystore option. The new password is identified by the new_storepass keyword. The password should be at least six characters long. Now that I’ve discussed all the options of the keytool command, let’s discuss the use of this command.
211
212
Part II
ADVANCED JAVA SECURITY CONCEPTS
Use of the keytool Command I’ll emphasize the use of the keytool command in this section. Imagine that you have to create a keystore for managing your public/private key pairs. The keystore will also manage the trusted certificates. Therefore, the first step that you need to take is to create a keystore and generate a key pair.
Create a Keystore and Generate a Key Pair To create a keystore and to generate a key pair, specify the following command at the command prompt: keytool -genkey -keystore myStore -alias trialKey
The preceding command requires you to enter certain information, which is illustrated in Figure 9-1. The preceding command creates a keystore named myStore. Because the location of the keystore is not defined in the command, the default directory from where the command is executed is the location of myStore. In the preceding example, C: is the location where the keystore is located after the execution of the command. The password, awe345, is assigned to the keystore. The preceding command also generates the public/private key pair with the alias, trialKey, for the entity defined by the common name of Caryl Johnson. The other details of this entity, such as the organizational unit or organization, are also defined. The private key of this pair is assigned the password, ret345.
FIGURE 9-1 Using the keytool command with the -genkey option
AN INTRODUCTION TO JAVA 2 SECURITY TOOLS
Chapter 9
The preceding command generates a self-signed certificate, which includes the public key and the distinguished name information. Because the validity limit is not specified in the preceding command, the default limit of 90 days is assigned to the certificate. I’ve discussed keytool and its options in the preceding section. I’ll now discuss the jar tool.
The jar Tool The main function of the jar tool is to store multiple files in a single Java ARchive ( JAR) file. The jar tool is a compression and archiving tool. It is based on the ZIP compression format. You can use the jar tool for zipping files so that tasks such as data compression, archiving, unpacking, and decompression can be performed on the resultant JAR files. You are already aware that the components of applets and applications can be stored together in a JAR file. This improves the download time because the browser can download the entire application or applet with all its components in a single HTTP transaction. If it were not for JAR files, it would take many transactions to download an entire application or applet with all its components. Using the jar tool, you can perform certain basic operations on JAR files. These operations are: ◆ Creating JAR files ◆ Viewing the contents of JAR files ◆ Extracting the contents of JAR files ◆ Updating JAR files ◆ Invoking the applets packaged in a JAR file ◆ Running an application packaged as a JAR file
Let’s discuss the use of the jar tool in the preceding operations.
Creating JAR Files The syntax for using the jar command to create JAR files is specified here: jar cf [jar-file] [input-file(s)]
I’ll now explain the meaning of all the options, which the jar command-line tool uses while creating JAR files. The various options and their descriptions are listed in Table 9-1.
213
214
Part II
ADVANCED JAVA SECURITY CONCEPTS
Table 9-1 Options Supported by the jar Command Option
Description
c
Creates a JAR file.
f
Directs the output to a JAR file instead of stdout.
v
Produces a verbose output on stdout.
0
Stores the files in a JAR file without compressing them.
M
Indicates that the default manifest file is not needed.
m
Includes manifest information from an existing manifest file.
C
Changes the directories during the execution of a program.
I’ll consider an example to explain the use of the jar tool during the creation of JAR files. For example, all the class files in the current directory need to be added to a JAR file. The command for this task is as follows: jar cvf classes.jar *.class
In the preceding example, all the class files in the current directory are stored in the classes.jar file. A manifest file is automatically created by the preceding command and is the first entry in the JAR file. The name of this manifest file is manifest.mf by default.
NOTE All meta-information for an archive is stored in the manifest file. The manifest file contains a list of files present in the archive. Basically, the files to be signed need to be included in the manifest file. For more information on the manifest file, you can visit http://java.sun.com/products/jdk/1.2/docs/guide/jar/manifest.html.
You are aware that the v option with the jar command produces a verbose output. Figure 9-2 displays the output of the jar command with the v option. The preceding figure shows that the classes.jar file produced is compressed. However, you can avoid this compression by the following command. jar cvf0 classes.jar *.class
The 0 option stores the files in a JAR file without compressing them. Refer to Figure 9-3 and compare this output with Figure 9-2.
AN INTRODUCTION TO JAVA 2 SECURITY TOOLS
Chapter 9
FIGURE 9-2 Using the v option with the jar command
FIGURE 9-3 Using the 0 option with the jar command
Let’s consider another example where the C:\clock directory contains the class files and images directory of the Clock applet. The following code statement creates a clock.jar file that packages this applet. jar cvf clock.jar *.class images
The preceding command stores the class files and the images directory in the clock.jar file. Therefore, the image files are stored in the images directory in the resulting JAR file as shown in Figure 9-4. The preceding example shows that the JAR files retain their relative pathnames and directory structure. However, you can change this by using the -C option with the jar command. Consider the following code statement: jar cf images.jar -C images
215
216
Part II
ADVANCED JAVA SECURITY CONCEPTS
FIGURE 9-4 The creation of clock.jar file with the images directory
You’ll have to issue the preceding command from the parent directory of the images directory. The -C option directs the jar tool to the images directory and the . (period) following the -C images text directs the tool to archive all the contents of the images directory. In this section, you learned about the different options used for creating JAR files. In the next section, I’ll discuss how to view the contents of JAR files.
Viewing the Contents of JAR Files You can view the contents of a JAR file by using the following command: jar tf [jar-file]
As seen in the preceding code statement, there are two options that you see with the jar command. Let’s discuss these options. ◆ The t option specifies that you wish to view the table of contents of the
specified JAR file. ◆ The f option indicates that the JAR file whose contents you wish to view is specified at the command line. If this option is missing, the jar tool expects
a file name to be provided by the standard input device. The command displays the contents of the JAR file on the standard output device. Consider the code statement given here: jar tf clock.jar
AN INTRODUCTION TO JAVA 2 SECURITY TOOLS
Chapter 9
FIGURE 9-5 Contents of the clock.jar file
The preceding command displays the output of the JAR file, as shown in Figure 9-5. If you use the v option along with the preceding command, the jar tool displays additional information such as the file size and the modification date. Consider the following code statement: jar tvf clock.jar
The output of the preceding command with the v option is shown in Figure 9-6. You can compare Figures 9-5 and 9-6 to understand the difference between the two outputs. You have learned to create and view JAR files. Now, I’ll discuss how to extract the contents from JAR files in the subsequent section.
FIGURE 9-6 Contents of the clock.jar file with the v option of the jar tool
217
218
Part II
ADVANCED JAVA SECURITY CONCEPTS
Extracting the Contents of JAR Files The syntax for using the jar command to extract fields from a JAR file is as follows: jar xf [jar-file] [archived-file(s)]
The preceding command has two new options, which are discussed here: ◆ The x option indicates that files need to be extracted from the specified JAR
file. ◆ The f option specifies that the JAR file from which the files are to be
extracted are specified at the command prompt. If this option is not specified, the file is supplied through the standard input device. In the preceding command, [jar-file] is the JAR file from which you need to extract files, and [archived-file(s)] are the files that need to be extracted from the archive file. When the jar tool extracts files, the original JAR file is not changed. Copies are made of the desired files and are written to the current directory in a similar directory structure as in the original JAR file. Consider the code statement given here: jar xf clock.jar ImageItem.class images/web.gif
The preceding command creates a copy of the ImageItem.class in the current directory. It also creates an images folder if it does not already exist and stores a copy the web.gif image in this directory. However, while extracting files, the clock.jar file is unchanged.
Updating JAR Files You can update an existing JAR file by adding new files to it. The syntax of the jar tool command for updating JAR files is specified here: jar uf [jar-file] [new-file(s)]
There is a new option specified in the preceding syntax. The u option indicates that you need to update the specified JAR file by adding the files specified by the [newfile(s)] keyword. If you try adding a file that already exists, the earlier file is overwritten by the latest version of the file. If you want to modify the manifest file contained in the JAR file, you can use the m option. Consider the code statement specified here: jar umf manifest [jar-file]
AN INTRODUCTION TO JAVA 2 SECURITY TOOLS
Chapter 9
In this case, the file specified by the manifest keyword does not replace the existing manifest file in the specified JAR file. Instead, the contents of the latest manifest file are merged into the earlier manifest file. Consider the code statement given here: jar uf clock.jar images/cart.gif
This statement adds the cart.gif file in the images directory to the clock.jar file. You can view the contents of the clock.jar file to verify whether the file has been added. Refer to Figure 9-7 for details.
FIGURE 9-7 Updating the clock.jar file and verifying its contents for
the new image added
I’ve already discussed the creating of JAR files. The next section covers how to run the applets and the applications packaged in these JAR files.
Running the Software Packaged in JAR Files You are already aware that you can store both applets and applications in a JAR file. You might now be wondering how to run a JAR file in a browser or at the command prompt. First, I’ll discuss running the applets packaged in JAR files. When you run an applet inside a browser, you need to specify the APPLET tag in the HTML file. On the other hand, if the applet is packaged in a JAR file, you can specify the relative path of the JAR file by using the Archive parameter. For example, consider an applet, Clock, with the class, Clock.class. The APPLET tag in the HTML file is specified here:
219
220
Part II
ADVANCED JAVA SECURITY CONCEPTS
The following would be the APPLET tag specification for the Clock applet that is bundled in a JAR file.
The preceding example assumes that the JAR file and the HTML file are in the same location. However, if they are in different locations, you need to specify the relative path of the JAR file. You can run Java applications packaged in JAR files by using the following command: java -jar [jar-file]
The -jar flag indicates that the application to be executed is in the specified JAR file. How do you know the class that begins execution in a Java application? To indicate this class, you need to add the Main-Class header in the manifest of the specified JAR file. The header is specified as: Main-Class: Clock
Therefore, the application starts execution from the Clock class. I’ll now discuss the jarsigner tool in the subsequent section.
The jarsigner Tool You are already aware that the jarsigner tool is used to sign the JAR files created by the jar tool. It is also used to verify the signatures on these JAR files. A digital signature is a string of bits that is derived from the data that needs to be signed by the private key of a person or company. A digital signature has many unique features that help in maintaining the security for any application. These features are listed here: ◆ A digital signature can be easily authenticated because it is derived from a
computation. Therefore, it can also be verified by a computation. This computation uses the public key associated with the private key that is used to generate the signature. ◆ The data signed by a digital signature cannot be changed; otherwise, it will
not be authenticated in the verification phase. ◆ A digital signature is unique to the data it signs. It cannot be used to sign
another piece of data.
AN INTRODUCTION TO JAVA 2 SECURITY TOOLS
Chapter 9
◆ A digital signature cannot be forged because the private key is kept secret by
password protection. To sign the data, a person or company should have a public/private key pair associated with the signature and certificates authenticating the public key. The jarsigner tool uses the certificate and key information from the keystore to sign JAR files. This tool basically uses the private key of an entity to generate a digital signature. Note that the jarsigner tool can only sign the JAR files created by the jar tool. A signed JAR file contains a copy of the certificate from the keystore. This certificate is for the public key associated with the private key that is used to sign the JAR file. There are many code signing algorithms. However, the jarsigner tool can use either the RSA algorithm with the MD5 digest algorithm or the Digital Signature Algorithm (DSA) with the SHA-1 digest algorithm. The basic syntax of the jarsigner tool is specified here: jarsigner options jar-file alias
The options keyword specifies the additional options that can be used with this command. These options will be discussed in the subsequent section. The jar-file keyword represents the JAR file that needs to be signed. The alias keyword represents the alias identifying the private key that is used to sign the file. It also identifies the associated public key and keystore certificate. The basic syntax of the jarsigner tool when it is used for signature verification is given here: jarsigner -verify [options] jar-file
I’ll now discuss the contents of a signed JAR file after the jarsigner tool signs it.
Contents of Signed JAR Files The output JAR file that is signed is similar to the original unsigned JAR file, except that two additional files are placed in the META-INF directory. These files are a signature file with an .SF extension and a signature block file with a .DSA extension. The names of these files are derived from the -sigfile option. Consider the code here, which is a part of the jarsigner command: -sigfile classes
The preceding code creates two files, namely classes.SF and classes.DSA. The SF file is similar to the manifest file contained in any JAR file. It contains the file name, the name of the SHA digest algorithm used, and the SHA digest value. The DSA file
221
222
Part II
ADVANCED JAVA SECURITY CONCEPTS
contains the signature after the SF file is signed. It also contains keystore information, such as the public keys and the certificates associated with the private key that is used to sign the specified JAR file. This information is encoded in the DSA file. Multiple people can sign a specified JAR file. All you have to do is execute the jarsigner command on the specified JAR file multiple times, specifying a different alias every time. For example, consider the code statements specified here: jarsigner classes.jar Laurie jarsigner classes.jar Caryl jarsigner classes.jar Michael
In the case of multiple signatures, there are multiple SF and DSA files because they are generated for every signature. The classes.jar file then contains the files: Laurie.SF, Laurie.DSA, Caryl.SF, Caryl.DSA, Michael.SF, and Michael.DSA. Let’s discuss the various options associated with the jarsigner command-line tool.
Options Associated with the jarsigner Tool When you simply enter the jarsigner command at the command prompt without any other options, an entire listing of the various command options is displayed at the command prompt. Refer to Figure 9-8 for the same.
FIGURE 9-8 Options associated with the jarsigner command
AN INTRODUCTION TO JAVA 2 SECURITY TOOLS
Chapter 9
Let’s discuss the use of these options in detail. Certain features are common to all the options. All the options have a minus sign (-) before them. The options can be provided in any order. The values following the specified option are the values that must be supplied with the command.
The -keystore Option The syntax of this option is specified here: -keystore
This option specifies the URL of the keystore location. The default keystore is stored as a .keystore file in the user’s home directory. If the default keystore is missing, you need to specify a keystore location using this option. The keystore is required while signing files. However, it is not needed when the jarsigner command is used to verify signatures. The in the preceding code statement can be replaced either by a file name or a path name. It can also be replaced by a file URL.
The -storepass Option The syntax of this option is specified here: -storepass <password>
This option specifies the password used to protect the keystore. This password is needed when the keystore is accessed while signing a JAR file. It is not needed when the jarsigner command verifies the signature of a JAR file. If this option is not specified, the user is prompted for the password.
The -storetype Option The syntax of this option is specified here: -storetype
This option specifies the type of keystore to be used while signing JAR files. The default keystore file is saved in the user’s home directory. The default keystore type is identified by the value contained in the keystore.type property in the security properties file. The getDefaultType() method of the java.security.KeyStore class returns this value.
The -keypass Option The syntax of this option is specified here: -keypass <password>
223
224
Part II
ADVANCED JAVA SECURITY CONCEPTS
This option specifies the password for the private key that is used by the jarsigner tool to sign files. This private key belongs to the keystore entry identified by the alias name specified in the jarsigner command. If this option is not specified and the private key password is different from that of the keystore password, the user is prompted for the password. The keystore and private key password should be handled with care and secrecy. You should not type the password in front of anyone to maintain its secrecy. This is because the password entered at the prompt is not hidden by asterisks, instead it is echoed on the screen. Make it a habit to close the command prompt window after issuing any such command. Otherwise, any user can scroll up to see the password information.
The -sigfile Option The syntax of this option is specified here: -sigfile
This option has already been discussed in the preceding section. It specifies the base name to be used for the SF and DSA files. The keyword specified in the preceding code statement can take characters such as letters, a hyphen, an underscore, and numbers. All lowercase characters are automatically converted to uppercase characters for SF and DSA file names. If this option is not specified, the base name for these files is the same as the first eight characters of the alias name specified by the jarsigner command. If the alias name has less than eight characters, the full alias name is used. The alias name is automatically converted to uppercase characters. If the alias name contains any restricted characters, these are replaced by the underscore (_) character while forming the basic name.
The -signedjar Option The syntax of this option is specified here: -signedjar
This option specifies the name to be used for the signed JAR file. If this option is not specified, the original file name is retained. The signed JAR file replaces the original JAR file.
The -verify Option When this option is specified along with the jarsigner command, it indicates that the JAR file provided will be verified for its signature. If the JAR file supplied is not
AN INTRODUCTION TO JAVA 2 SECURITY TOOLS
Chapter 9
signed or is signed with an unsupported algorithm, a suitable message is displayed on the screen. The message, jar verified, is displayed when the JAR file verification is successful.
The -verbose Option This option indicates that a verbose output will be supplied at the command prompt. When this option is specified, the jarsigner command displays additional information related to signing and verification.
The -certs Option This option displays certificate information for every signer of the JAR file. This is possible only when the -verify and -verbose options are also specified with this option. The information displayed contains the distinguished name of the signer, if the certificate is a X.509 certificate. Information such as the type name of the certificate that certifies the signer’s public key is also printed.
The -internalsf Option In the previous versions, the DSA file contained an encoded copy of the SF file. However, this increased the overall size of the output JAR file. Therefore, now the DSA file does not contain a copy of the SF file. However, the previous version functionality—the DSA file that contained an encoded copy of the SF file—can be restored by using this option.
The -sectionsonly Option This option does not allow the SF file to include the header containing the hash of the manifest file. Therefore, with this option, the SF file contains only information and hashes related to each individual source file created in the JAR file.
Use of the jarsigner Tool The security tools, namely the keytool, jar, and jarsigner tools, work collectively to ensure the security of the system. For example, you can use data or code downloaded from a Web site or the network. This signed code and data can be verified for authenticity and integrity by the jarsigner tool. Consider a situation where you create a JAR file containing all the class files and images of an application. You transfer this file over the network to a destination location. For security purposes, you need to send your authenticity certificate along with the JAR file to the destination location. You sign the JAR file and export the associated certificate to the destination location. The user at
225
226
Part II
ADVANCED JAVA SECURITY CONCEPTS
the destination location imports the certificate and verifies the signature on the signed JAR file. The entire process discussed is outlined by a series of keytool and jarsigner commands. I’ll discuss the various steps involved in the preceding example. You are already aware of the use of the keytool command to create a keystore and a private key pair. Imagine that you have already created a keystore named myStore, which has a key pair and an associated self-signed certificate. To review, a keystore is created by using the keytool -genkey option. The key pair and the self-signed certificate are associated with the alias, CJKey. The generation of the keystore, key pair, and self-signed certificate is illustrated in Figure 9-9. Next, you need to sign the JAR file with the private key created. Assume that you have created a classes.jar file. To sign the JAR file with the private key generated, you’ll write the following command at the command prompt. jarsigner -keystore myStore classes.jar CJKey
The output is displayed in Figure 9-10. Therefore, the classes.jar file is signed by using the private key information from the myStore keystore. Even though you did not specify the private key password in the command, you were not prompted for it because it is same as the keystore password. Now, the JAR file is signed and the self-signed certificate is also created. However, you need to export this self-signed certificate to a file so that you can send it to the destination. This can be done by using the keytool command as specified here: keytool -export -keystore myStore -alias CJKey -file Caryl.crt
The output is displayed in Figure 9-11.
FIGURE 9-9 Creation of the key pair and self-signed certificate
AN INTRODUCTION TO JAVA 2 SECURITY TOOLS
Chapter 9
FIGURE 9-10 Signing the classes.jar file with the CJKey
FIGURE 9-11 Exporting self-signed certificate to a file
Therefore, the preceding command creates a file, Caryl.crt, with the self-signed certificate. Now, you can send the signed classes.jar file along with your authentication certificate, which is stored in the Caryl.crt file, to the destination location. The person receiving the JAR file and certificate needs to verify the certificate and import it to the keystore on his/her computer. The following command performs this operation: keytool -import -file Caryl.crt -keystore destStore
The output is displayed in Figure 9-12. As you can see from the preceding figure, the file containing the self-signed certificate is imported to the keystore, destStore, on the destination computer.
227
228
Part II
ADVANCED JAVA SECURITY CONCEPTS
FIGURE 9-12 Importing a self-signed certificate to the destination keystore
The preceding command asks the users whether they trust the certificate. The certificate is imported to the keystore only if the user enters y. Certificate information such as the owner, issuer, validity, and fingerprints are also displayed at the command prompt. Because no alias is mentioned for destStore, the certificate is stored in the keystore with the default alias, myKey. The person receiving the certificate information should first verify it before trusting it and adding it to the keystore. This can be done by asking the sender to mail the certificate information such as the fingerprints. The two sets of fingerprints can then be compared to verify that the certificate has not been tampered with during transit. The final step is to verify the signature on the signed JAR file. This can be done by the following command: jarsigner -verify -verbose -keystore destStore classes.jar
The output is displayed in Figure 9-13. If the JAR file is not signed, the preceding command will display the output as in Figure 9-14. On the other hand, if the files in the JAR file have been modified, the verification of the JAR file will result in an error. The last security-related tool is the policy tool. I’ve already discussed the policy tool in detail in Chapter 1, “Security: An Overview”. You are aware that the policy tool is used to create a policy file. You can add policy entries, modify them, delete them, and change the keystore by using the policy tool.
AN INTRODUCTION TO JAVA 2 SECURITY TOOLS
Chapter 9
FIGURE 9-13 Verifying the signature of the classes.jar file
FIGURE 9-14 Verifying the signature of the unsigned classes.jar file
Summary In this chapter, I discussed the various security tools in Java 2. I discussed the keytool command with its various options. The keytool command is used to manage the keystore. Then, I discussed the jar tool, which is used to create JAR files. I discussed all the operations performed by the jar tool. Finally, I discussed the jarsigner tool, which is used to sign JAR files and verify the signatures on them.
229
230
Part II
ADVANCED JAVA SECURITY CONCEPTS
Check Your Understanding Multiple Choice Questions 1. Which of the following keytool command options is used to generate a key pair that constitutes the public key and its associated private key? a. -certreq b. -genkey c. -import d. -export 2. The f option of the jar command does which of the following? a. Creates a JAR file. b. Stores the files in a JAR file without compressing them. c. Directs the output to a JAR file instead of stdout. d. Includes manifest information from an existing manifest file. 3. Which of the following code statements displays the contents of the JAR file on the standard output device? a. jar tf [jar-file] b. jar t [jar-file] c. jar cf [jar-file] [input-file(s)] d. jar tvf [jar-file] 4. What does the following code statement do? jar xf myfile.jar MyItem.class images/myimage.gif
a. Runs the Java application packaged in the myfile.jar file. b. Creates a copy of the MyItem.class in the current directory. It also creates an images folder if it does not already exist and stores a copy of the myimage.gif image in this directory. c. Lists the contents of the myfile.jar file and stores the images in the images folder. d. Adds the myimage.gif file in the images folder to the myfile.jar file. 5. Which of the following statement(s) is true about the jarsigner tool? a. The jarsigner tool uses the certificate and key information from the keystore to sign JAR files. b. The jarsigner tool can only use the RSA algorithm with the MD5 digest algorithm.
AN INTRODUCTION TO JAVA 2 SECURITY TOOLS
Chapter 9
c. The jarsigner tool can only sign the JAR files created by the jar tool. d. The jarsigner tool can sign the JAR files created by other tools as well. 6. What is the use of the -sigfile option with the jarsigner tool? a. This option specifies the base name to be used for the SF and DSA files. b. This option does not allow the SF file to include the header containing the hash of the manifest file. c. This option specifies the password for the private key that is used by the jarsigner tool to sign files. d. This option displays certificate information for every signer of the JAR file.
Short Questions 1. The C:\Accounts directory contains the class files and images directory of the Accounts applet. Create an accounts.jar file that packages the class files and images of this applet. Extract the MyAcc.class file and Acc.gif from the accounts.jar file. Add a new image file, accsheet.gif, to the accounts.jar file. 2. What do signed JAR files contain? 3. Explain the difference of running Java applets and applications packaged in JAR files.
Answers Multiple Choice Answers 1. b. The -genkey option generates a key pair that constitutes the public key and its associated private key. 2. c. The f option of the jar command directs the output to a JAR file instead of stdout. 3. a. The jar tf [jar-file] statement displays the contents of the JAR file on the standard output device. 4. b. The code statement creates a copy of the MyItem.class in the current directory. It also creates an images folder if it does not already exist and stores a copy of the myimage.gif image in this folder. 5. a and c. The jarsigner tool uses the certificate and key information from the keystore to sign JAR files. This tool basically uses the private key of an
231
232
Part II
ADVANCED JAVA SECURITY CONCEPTS
entity to generate a digital signature. Note that the jarsigner tool can only sign the JAR files created by the jar tool. A signed JAR file contains a copy of the certificate from the keystore. This certificate is for the public key associated with the private key that is used to sign the JAR file. There are many code signing algorithms. However, the jarsigner tool can use either the RSA algorithm with the MD5 digest algorithm or the Digital Signature Algorithm (DSA) with the SHA-1 digest algorithm. 6. a. The -sigfile option specifies the base name to be used for the SF and DSA files.
Short Answers 1. The C:\Accounts directory contains the class files and images directory of the Accounts applet. To create an accounts.jar file that packages the class files and images of this applet, you’ll use the following code statement: jar cvf accounts.jar *.class images
The preceding command stores the class files and the images directory in the accounts.jar file. To extract the MyAcc.class file and Acc.gif from the accounts.jar file, you’ll use the following code statement: jar xf accounts.jar MyAcc.class images/ Acc.gif
To add a new image file, accsheet.gif, to the accounts.jar file, you’ll use the following code statement: jar uf accounts.jar images/ accsheet.gif
2. The output JAR file that is signed is similar to the original unsigned JAR file, except that two additional files are placed in the META-INF directory. These files are a signature file with an .SF extension and a signature block file with a .DSA extension. The names of these files are derived from the sigfile option. Consider the code here, which is a part of the jarsigner command: -sigfile clock
The preceding code creates two files, namely classes.SF and classes.DSA. The SF file is similar to the manifest file contained in any JAR file. It contains the file name, the name of the SHA digest algorithm used, and the SHA digest value. The DSA file contains the signature after the SF file is signed. It also contains keystore information, such as the public keys and the
AN INTRODUCTION TO JAVA 2 SECURITY TOOLS
Chapter 9
certificates associated with the private key that is used to sign the specified JAR file. This information is encoded in the DSA file. 3. You can store both applets and applications in a JAR file. When you run an applet inside a browser, you need to specify the APPLET tag in the HTML file. On the other hand, if the applet is packaged in a JAR file, you can specify the relative path of the JAR file by using the Archive parameter. For example, consider an applet, Game, with the class, Game.class. The APPLET tag in the HTML file is specified here:
The following would be the APPLET tag specification for the Game applet that is bundled in a JAR file:
The preceding example assumes that the JAR file and the HTML file are in the same location. However, if they are in different locations, you need to specify the relative path of the JAR file. You can run Java applications packaged in JAR files by using the following command: java -jar [jar-file]
The -jar flag indicates that the application to be executed is in the specified JAR file. To know the class that begins execution in a Java application, you need to add the Main-Class header in the manifest of the specified JAR file. The header is specified as: Main-Class: Game
Therefore, the application starts execution from the Game class.
233
This page intentionally left blank
Chapter 10 An Introduction to Java Plug-ins
Y
ou are aware that Sun releases new versions of Java SDK from time to time. Browser vendors try to keep up with these versions by incorporating changes in their browser software almost simultaneously. However, their browsers are unable to keep up with all the changes made in the new version release of Java SDK. Therefore, corporate intranet developers are often deprived of these new Java SDK features because the corresponding changes are not present in the new browser release.
Sun has solved this problem by introducing the plug-in architecture via the Java plugin. The Java plug-in is software from Sun Microsystems that allows Web developers to run Java applets or JavaBeans components in their Web pages by using the JRE instead of the browser’s default JVM. This software comes with the installation of JRE 1.2x and Java 2 SDK. In this chapter, I’ll discuss features of the Java plug-in in detail. I’ll also explain the role of the Java plug-in.
The Features of the Java Plug-in The Java plug-in acts as a layer between the JRE and the browsers. It integrates all the features and capabilities of Java 2 in popular browsers such as Netscape Navigator and Microsoft Internet Explorer. Thus, when Sun adds new features to the Java 2 SDK, developers can access these features in Internet Explorer and Navigator by using the latest release of the Java plug-in. Now, let’s discuss the basic features of the Java plug-in. ◆ The Java plug-in provides full support to developers for creating applets that
use all the features of Java 2 SDK version 1.2 and higher. This support is provided both on Netscape and Internet Explorer. The plug-in allows signed classes to be loaded in these browsers, and these applets are subject to the recent changes in the Java 2 security model. Before the emergence of the Java plug-in, developers could not use the new features of the new version release because these features were not incorporated in their browser. ◆ The Java plug-in provides a future-ready Java 2 SDK architecture. This
indicates that whenever a new version of Java 2 with new features enters the market, these features are immediately available on popular browsers by using the Java plug-in.
AN INTRODUCTION TO JAVA PLUG-INS
Chapter 10
◆ The Java plug-in provides an easy download and install procedure. When a
Web page that requires Java plug-in support is encountered, the browser downloads and installs all the necessary files. In this way, there is minimal user intervention when a particular applet is being executed. ◆ The Java plug-in provides a free Java plug-in HTML converter that modi-
fies the HTML pages that are using Java plug-ins. This saves the overhead of modifying the browser’s default JRE environment. Now, let’s discuss how to install and run the Java plug-in on both Internet Explorer and Netscape Navigator.
Installing the Java Plug-in I’ve already discussed that when an applet requiring Java plug-in support is loaded in a browser, the browser downloads and installs all the necessary files. Please note that this holds true only if the Java plug-in is not already installed. I’ll first discuss the process in Netscape Navigator. When Navigator encounters a Web page that requires plug-in support, a plug-in missing picture and the Plug-in Not Loaded dialog appears on the page that prompts for installing the plug-in from the Sun site. Refer to Figure 10-1 for this prompt. Users can click the Get the Plugin button to start the Java plug-in download process. Once the plug-in is installed, the prompt in Figure 10-1 will not appear again. Therefore, the applets requiring plug-in support will execute without user intervention. Now, I’ll discuss the Java plug-in download process in Internet Explorer. When Internet Explorer encounters a Web page that requires plug-in support, a Security Warning dialog appears, asking whether the user needs to download an ActiveX control that is digitally signed by Sun Microsystems and can be verified by using the associated VeriSign certificate. Refer to Figure 10-2 for this security warning. When the user clicks the Yes button in the Security Warning dialog, an installation dialog appears, as in Figure 10-3.
FIGURE 10-1 The Plugin Not
Loaded message box
237
238
Part II
ADVANCED JAVA SECURITY CONCEPTS
FIGURE 10-2 The Security Warning dialog in
Internet Explorer
FIGURE 10-3 The Java Plugin
Installation dialog
This dialog box allows you to select the appropriate locale-specific JRE. When the user clicks on the Install button, a Downloading dialog appears, as shown in Figure 10-4. The plug-in and the JRE ActiveX files are automatically downloaded and installed. The download and installation time depends upon the network connection and overall system performance. The next time when Internet Explorer encounters a Web page that requires plug-in support, it automatically loads and runs the downloaded ActiveX control. Now, let’s discuss what the Java plug-in actually does to fulfill its purpose.
AN INTRODUCTION TO JAVA PLUG-INS
Chapter 10
FIGURE 10-4 The Downloading dialog
The Role of the Java Plug-in The main role of the Java plug-in is to provide applets support for all the features and capabilities in Java 2 SDK. This cannot hold true if the particular Web page uses the default runtime supported by the browser because these changes have not been incorporated in it. However, the Java plug-in does not replace or modify the default browser runtime. Using the Java plug-in, Web page developers can specify the use of Sun’s JRE instead of the default runtime. Let’s discuss how the browsers, Netscape Navigator and Internet Explorer, use Sun’s JRE in Web pages. You can run Sun’s JRE in Navigator by using its plug-in architecture. Web page developers can use the <EMBED> HTML tag in place of the <APPLET> tag to run plug-ins as part of a Web page. Therefore, using this tag, Web developers can also run the Sun’s JRE in Navigator. You can use Microsoft technologies, such as COM and ActiveX, to allow Sun’s JRE to run within Internet Explorer. Web page developers can use the