One of the most appealing features of Java in its debut as a Web programming language was the comprehensive security built into the Java runtime environment. The Java sandbox provided a mechanism for untrusted code to be downloaded from the Web and executed in a secure manner. The sandboxes of JDK 1.0 and 1.1 had some holes, but Sun encouraged the Internet community to find those holes and then quickly fixed them. The security model implemented by the Java sandbox has been strengthened and at the same time made more flexible from JDK 1.0 to JDK 1.2. In addition, JDK 1.2 provides a number of security mechanisms that can be used within the sandbox.
In this chapter you'll study the new security features of JDK 1.2 that can be used to secure Java applets and applications. You'll learn how to specify the security policy for your Java installation and how to use permissions to implement fine-grain access controls that extend the capabilities permitted to certain applets. You'll learn about the use of digital certificates and the cryptographic architecture supported by the JDK 1.2 Security API. When you finish this chapter, you'll understand how to use the security features of JDK 1.2 to enhance the security and functionality of your applets and applications.
One of the most powerful security features introduced in JDK 1.2 is the capability to specify a security policy for applets and applications. This feature gives software developers a great deal of flexibility in the functionality that they can incorporate into their applets and applications. At the same time, it provides users with total control over the access they allow to these programs. The configurable security policy of JDK 1.2 allows Java software developers to provide the capabilities their users want, and enables users to limit those capabilities based on their degree of trust in the source of the Java software they execute.
To understand how the configurable security policy works and why it is useful, it is helpful to trace the evolution of Java security. JDK 1.0 introduced the "sandbox" approach to applet security. In this approach, all standalone Java applications are trusted by default and are allowed unrestricted access to your system resources (file system, network, and other programs). Applets that are loaded over the network are, by default, untrusted and prevented from accessing your local file system and other programs. In addition, applets are only allowed to make network connections to the host from which they are loaded.
The objective of the JDK 1.0 sandbox is to protect users from malicious applets that are downloaded from the Web. With the exception of a few security holes (which were subsequently corrected), the JDK 1.0 sandbox met this objective. However, in blocking potentially hostile applet accesses, the 1.0 sandbox also removed useful applet capabilities. Figure 3.1 summarizes the operation of the JDK 1.0 sandbox.
FIGURE 3.1. The JDK 1.0 sandbox.
In addition to extending the sandbox for signed applets, JDK 1.0 also allows
the SecurityManager class to be subclassed to implement a custom security policy
for standalone Java applications, such as those that load applets. If SecurityManager
is overridden, the capabilities of standalone applications can be restricted. However,
the capability to implement a custom SecurityManager is provided for software developers
but not users. If a user runs a standalone Java application, it is executed with
unrestricted privileges unless the application polices itself.
For an example of the security provided by the JDK 1.0 sandbox, consider the applet shown in Listing 3.1. This applet reads the file specified in an applet's fileName parameter and displays the file's contents in a TextArea object. (If a security exception occurs, it is displayed in the TextArea object instead.) When you view the applet using Microsoft Internet Explorer 4.0, you receive the security exception shown in Figure 3.2. The applet in Listing 3.1 and the HTML file (used to access the applet) in Listing 3.2 can be accessed on my Web server at http://www.jaworski.com/ju/ ReadFileApplet.htm. You can also set the applet up on your Web server using the files contained in the \ju\ch03 directory of this book's CD-ROM.
FIGURE 3.2. A security exception is thrown when an applet tries to go outside of the sandbox.
import java.applet.*; import java.awt.*; import java.awt.event.*; import java.io.*; public class ReadFileApplet extends Applet { TextArea text = new TextArea(); Button goButton = new Button("Read Local File"); Panel panel = new Panel(); String fileName = ""; public void init() { fileName = getParameter("fileName"); setLayout(new BorderLayout()); goButton.addActionListener(new ButtonHandler()); panel.add(goButton); add("North",panel); add("Center",text); } class ButtonHandler implements ActionListener { public void actionPerformed(ActionEvent e){ String s = e.getActionCommand(); if("Read Local File".equals(s)){ try { FileInputStream inStream = new FileInputStream(fileName); int inBytes = inStream.available(); byte inBuf[] = new byte[inBytes]; int bytesRead = inStream.read(inBuf,0,inBytes); text.setText(new String(inBuf)); }catch(Exception ex){ text.setText(ex.toString()); } } } } }
<HTML> <HEAD> <TITLE>An Applet that reads local files</TITLE> </HEAD> <BODY> <H1>An Applet that reads local files.</H1> <APPLET CODE="ReadFileApplet.class" HEIGHT=300 WIDTH=600> <PARAM NAME="fileName" VALUE="C:\AUTOEXEC.BAT"> Text displayed by browsers that are not Java-enabled. </APPLET> </BODY>
</HTML>
The JDK 1.1 sandbox is designed to maintain the security of the JDK 1.0 approach while allowing certain applets to be designated as trusted. Trusted applets are allowed to perform accesses that exceed the bounds of the sandbox. The Security API of JDK 1.1 provides the capability to digitally sign an applet and then verify that signature before an applet is loaded and executed. This capability enables browsers to authenticate that an applet is signed by a trusted party and that it has not been modified since the time of its signature. Given this additional level of security assurance, signed applets are considered to be as trustworthy as (or more than) standalone application programs. Figure 3.3 shows how the JDK 1.1 sandbox extends the JDK 1.0 sandbox.
FIGURE 3.3. The JDK 1.1 security approach.
When I configure Internet Explorer 4.0 to trust www.jaworski.com and to allow
applets from trusted hosts to access local files, I can execute the ReadFileApplet
in Listing 3.1 and have it read, as well as display the contents of my AUTOEXEC.BAT
file, as shown in Figure 3.4.
FIGURE 3.4. JDK 1.1 security allows trusted applets to go outside of the sandbox.
The JDK 1.1 security approach is a significant improvement on the JDK 1.0 approach because it allows applet designers to add useful capabilities like reading from and writing to the local file system, launching programs, and advanced networking. The shortcomings of the JDK 1.1 sandbox stem from its bipolar approach to security--an applet is either untrusted and confined to the sandbox, or it is trusted and given unrestricted access outside the sandbox. Applications are always trusted and given unrestricted access.
The problem with the JDK 1.1 approach is that it violates the security principle of least privilege. This principle states that an application should be given only those privileges that it needs to carry out its function and no more. According to least privilege, trusted applets and applications should be limited in the privileges they are allowed. For example, now that Internet Explorer 4.0 is reconfigured to run the ReadFileApplet, it will allow all applets from www.jaworski.com full access to my local file system. If Internet Explorer 4.0 implemented least privilege, I would be able to select the applets from www.jaworski.com that would have the privilege of accessing my local files.
JDK 1.2 introduces a security architecture for implementing least privilege. This architecture is based on the capability to specify a security policy that determines what accesses an applet or application is allowed, based on its source and on the identities of those who have signed the applet on application code.
The security policy feature of JDK 1.2 allows you to specify the following types of policies easily and without programming:
The next section shows how to specify the details of a particular security policy. Figure 3.5 shows how JDK 1.2 extends the JDK 1.0 and 1.1 sandboxes using a configurable security policy.
FIGURE 3.5. The JDK 1.2 configurable security policy.
NOTE: To restrict the capabilities of a Java application, you must run it under a SecureClassLoader, as described in the section "Application Security" later in this chapter.
Specifying a custom security policy is easy to do. All you have to do is edit the appropriate policy configuration file. JDK 1.2 provides you with a number of ways to do this:
java -Djava.security.policy=="test.policy" Test
When the Java byte code interpreter is run, it loads in the system policy followed by the user policy. If neither of these policies is available, the original sandbox policy is used.
You can also use the -Djava.security.manager option to ensure that the policy is installed. The double equal sign specifies that the policy should be the only policy that is in effect. A single equal sign specifies that the policy is added to the current policy that is in effect.
The policy file (system or user) consists of a series of statements, referred to as grant entries, that identify the permissions granted to code (applet or application) based on the location from which it is loaded and any signers of the code.
NOTE: In JDK 1.2, all code, whether it is an applet that is loaded from a remote host or an application from the local file system, is associated with a code source. This code source is defined by the URL from which the code is loaded and a list of signers of the code. These signers are identified by the names associated with the signer's public keys. These names are referred to as aliases. The aliases and keys are stored in a user's keystore, as shown in Figure 3.6.
FIGURE 3.6. The keystore stores aliases, keys, certificates, and other information about entities.
A keystore is a repository for the aliases, certificates, public keys, and other information about the entities (organizations and individuals) that are recognized by a user. A user's keystore resides in the .keystore file located in the user's home directory. The .keystore file is generated and maintained using JDK 1.2's keytool program. On Windows systems, the user's home directory is defined by the user.home property. If the HOMEDRIVE and HOMEPATH environment variables are defined, user.home is the concatenation of HOMEDRIVE and HOMEPATH. Otherwise, the value of user.home is the same as java.home.
NOTE: You'll learn more about the keytool in Chapter 8, "Applet Security."
The grant entries of the security policy identify a code source (URL and list of signers), followed by the permissions granted to that code source. The permissions (also referred to as permission entries) specify the actions that a code source may take with respect to a protected resource. If all this seems too abstract, hang in there. After we cover the syntax of grant entries and provide a few examples, the process of setting up a security policy will appear quite simple.
Grant entries begin with the keyword grant, followed by optional SignedBy or CodeBase clauses, followed by an opening bracket ({), followed by a list of permission entries, followed by a closing bracket (}) and a semicolon. The syntax of a grant entry follows:
grant [SignedBy "signer_names"] [, CodeBase "URL"] { permission entries };
The SignedBy clause contains a comma-separated list of the aliases of the signers of the code to which the grant entry applies. If the code has not been signed or the signers don't factor into the policy, the SignedBy clause may be omitted. Examples of SignedBy clauses follow:
SignedBy "Bill" SignedBy "Bill,Ted" SignedBy "Bill,Ted,Alice"
The aliases are not case sensitive. For example, "Bill" and "bill" are equivalent.
The CodeBase clause identifies the URL of the location from which the code is loaded. Examples follow:
CodeBase "http://www.trusted.com" CodeBase "http://www.trusted.com/omega/version5/" CodeBase "file:/local/applets/"
The first example specifies that the grant entry applies to all code that is loaded from www.trusted.com. The second example specifies that the grant entry applies to all code that is loaded from /omega/version5/ (and all subdirectories) of www.trusted.com. The third example specifies that the grant entry applies to all code that is loaded from the \local\applets directory (and all subdirectories) of the local file system.
If the CodeBase clause is omitted, the grant entry applies to all code locations. Note that syntactically the CodeBase clause may appear before the SignedBy clause. If both clauses are present, they are separated by a comma.
Each grant entry specifies one or more permission entries to define the permissions that are granted to the code source described by the SignedBy and CodeBase clauses. A permission entry consists of the keyword permission, followed by the fully qualified name of a Java permission class, followed by an optional target name, action list, and SignedBy clause. The syntax of a permission entry is as follows:
permission permission_class_name [ "target_name" ] [, "action_list"] [, SignedBy "signer_names"];
The permission class name identifies the permission to be granted. It is the fully qualified name of the Java class that implements the permission. Examples of permission class names follow:
java.io.FilePermission java.net.SocketPermission java.util.PropertyPermission
The java.io.FilePermission class is used to control the accesses that code may make of the local file system. The java.net.SocketPermission class is used to control the ways in which code may access TCP/IP sockets. The java.util.PropertyPermission class is used to control the ways in which code may access Java properties.
TIP: The implementation of permissions in terms of permission classes is covered in the section "Policy Permissions" later in this chapter. When reading it, just try to get a feel for what permissions are and how they are used to specify a security policy.
Two important characteristics of a permission are its target and its action list. The target identifies the resource or service to which access is being granted. For example, the target of java.io.FilePermission is the name of the file or directory to which access is being granted. The target of java.util.PropertyPermission is the property to which access is being granted.
The action list identifies the actions that the code source is permitted to make on the target. For example, actions related to java.io.FilePermission include read, write, delete, and execute. Actions related to java.net.SocketPermission include accept, connect, listen, and resolve. The action list is a comma-separated list of actions enclosed in quotation marks. Some permissions, such as java.RuntimePermission, are service-related and do not have actions. In these cases, the action list may be omitted.
NOTE: A complete list of the predefined permissions, targets, and actions is provided in the section "Policy Permissions" later in this chapter.
The last element of a permission entry is an optional SignedBy clause. Any SignedBy clause that is included with a permission entry indicates a signed permission entry. This means that the permission class itself must be signed by everyone in the specified signer list or must be a class that is found on the CLASSPATH. For example, the following permission entry requires the code for com.jaworski.DevicePermission.class to be signed by jamie or to be accessible from the CLASSPATH.
permission com.jaworski.DevicePermission "transmitter", "send, receive", SignedBy "jamie";
In this example, the com.jaworski.DevicePermission class has "transmitter" as its target and "send" and "receive" as the permitted actions.
Having covered the syntax of the security policy file, in this section I'll create an example policy and go through it on a line-by-line basis, explaining what its grant and permission entries mean and what the resultant policy permits and restricts. After that, I'll list and explain the java.policy file that is the default for JDK 1.2.
Listing 3.3 shows the example.policy file. It contains four grant entries. The first grant entry specifies the CodeBase as the URL http://www.trusted.com/verified/. This indicates that the grant entry applies to code that is loaded from that URL. Because there is no SignedBy clause, the grant entry applies to all code from this URL. The grant entry contains a single permission entry. This permission entry grants permission for the code to open up a client TCP socket to the host other.trusted.com on port 23 (the Telnet port).
The second grant entry specifies the CodeBase as the URL http://jaworski.com and the signer list as jason and emily. This indicates that the grant entry applies to all code that is loaded from jaworski.com that is signed by both Jason and Emily. The grant entry contains three permission entries. The first permission entry grants the code source permission to read, write, and delete all files and directories in the local file system. The "-" target indicates all files in the file system. The second permission entry grants the code source permission to read and write all Java properties. The "*" target indicates all properties. The third permission entry grants the code source permission to print to the local print queue.
The third grant entry has a CodeBase corresponding to all code that is loaded from the \local\trusted path of the local file system. There are no code signers. The grant entry has a single permission entry that grants the code source permission to create its own class loader.
The last grant entry does not specify a CodeBase or a signer list. This means that the grant entry applies to all code, no matter where it is loaded from or who signs it. The grant entry contains a single permission entry. This entry grants the code source permission to read the java.version property.
NOTE: You can use Java-style comments in a security policy configuration file.
// example.java
/* Grant all code that is from the /verified path of www.trusted.com permission to create a TCP socket and connect it to other.trusted.com on port 23. */ grant CodeBase "http://www.trusted.com/verified/" { permission java.net.SocketPermission "other.trusted.com:23", "connect"; }; /* Grant all code from jaworski.com that is signed by Emily and Jason the following permissions: 1. Permission to read, write, delete, or execute any file in the local file system. 2. Permission to read or write any property. 3. Permission to send a print job to the local print queue. */ grant CodeBase "http://jaworski.com", SignedBy "jason,emily" { permission java.io.FilePermission "-", "read,write,delete,execute"; permission java.util.PropertyPermission "*", "read,write"; permission java.lang.RuntimePermission "queueJob"; }; /* Grant all code from the /local/trusted path of the local file system permission to create its own class loader. */ grant CodeBase "file:/local/trusted" { permission java.lang.RuntimePermission "createClassLoader"; }; /* Grant all code from any location, signed or not, permission to read the java.version property. */ grant { permission java.util.PropertyPermission "java.version", "read"; };
Listing 3.4 shows the default java.policy file that comes with JDK 1.2. This file is located in the \jdk1.2\lib\security directory and contains a single grant entry. This grant entry does not specify a CodeBase or a list of signers. This means that the grant entry applies to all code. The grant entry contains 11 permission entries. The first permission entry grants permission for code to listen on TCP or UDP sockets on ports 1024 and higher. The target "localhost:1024-" identifies the host on which the code executes (using localhost) and ports 1024 and higher (using 1024-).
Permission entries 2 through 11 are similar. They grant permission to read the properties java.version through line.separator.
grant { // allows anyone to listen on un-privileged ports permission java.net.SocketPermission "localhost:1024-", "listen"; // "standard" properties that can be read by anyone permission java.util.PropertyPermission "java.version", "read"; permission java.util.PropertyPermission "java.vendor", "read"; permission java.util.PropertyPermission "java.vendor.url", "read"; permission java.util.PropertyPermission "java.class.version", "read"; permission java.util.PropertyPermission "os.name", "read"; permission java.util.PropertyPermission "os.version", "read"; permission java.util.PropertyPermission "os.arch", "read"; permission java.util.PropertyPermission "file.separator", "read"; permission java.util.PropertyPermission "path.separator", "read"; permission java.util.PropertyPermission "line.separator", "read"; };
Security policy is implemented by the java.security.Permission classes and their subclasses. The permissions classes are used to specify that permission has been granted to specific system services and resources. Most subclasses of java.security.Permission, such as java.net.SocketPermission and java.io.FilePermission, are defined in the packages that provide the services and resources being protected. The Permission class hierarchy is shown in Figure 3.7.
FIGURE 3.7. The Permission class hierarchy.
The java.security.Permisson class is subclassed by java.security. BasicPermission, java.io.FilePermission, and java.net.SocketPermission. The java.security.BasicPermission class provides a common subclass that implements permissions specifying targets that are named with the hierarchical naming convention used by properties and fully qualified class names. This naming convention separates naming levels using periods, as in level1name.level2name.level3name.
The java.security.BasicPermission class has seven subclasses that are identified in Figure 3.7. These seven classes, and the java.io.FilePermission and java.net.SocketPermission classes, are covered in the following subsections.
The java.security.AllPermission class defines a permission that implies all other permissions. This permission should be used only with extreme care.
The java.awt.AWTPermission class is used to control access to AWT resources. This class defines the following three targets:
None of these targets requires an action to be specified.
The java.net.NetPermission class is used to control access to network resources and defines the following targets:
Neither of these targets requires an action to be specified.
The java.util.PropertyPermission class is used to control access to system properties. The target of this permission is a system property. The wildcard "*" is used to specify all properties. A wildcard character may also appear at the end of a hierarchical property name following the last period. For example, "java.*" specifies all properties beginning with "java.". The read and write actions may be specified with the target property.
The java.lang.reflect.ReflectPermission class is used to circumvent the access checks performed on reflected objects. It allows all members of an object to be accessed, no matter what access is specified via the public, protected, and private keywords. Only the "suppressAccessChecks" target is defined for this permission. No actions may be specified. Reflection is covered in Chapter 10, "Writing Console Applications."
The java.lang.RuntimePermission class is used to control access to services of the Java runtime environment. It defines the following targets:
None of these targets requires actions to be specified.
This class is used to grant a variety of security-related permissions. These permissions are specified as hierarchical target names:
None of these targets requires actions to be specified.
This class is used to grant permission to use serialization. The enableSubstitution permission grants permission for objects in object input and output streams to be replaced. The enableSubclassImplementation permission allows subclasses of ObjectInputStream and ObjectOutputStream to override an object's serialization property.
The java.io.FilePermission class is an important class in that it is used to grant permission for file and directory operations. The targets for this permission are file and directory names. The "-" target is used to specify all files in the file system. The target "directory/-" is used to specify all files in the file system that are in directory or its subdirectories. The target "directory/*" is used to specify all files in the file system that are in directory but not its subdirectories.
The actions that can be specified are read, write, delete, and execute. The read, write, and delete actions apply to files and directories. The execute action applies to executable files and operating system commands.
THE JAVA.NET.SOCKETPERMISSION CLASS
The java.net.SocketPermission class is used to grant permission to TCP/IP socket programming. The targets of this permission are of the form "host" or "host:port_range", where host can be specified as one of the following:
* (All hosts)
The port range can be specified using the following notation (where n and m represent port numbers):
The actions that are defined for this permission are as follows:
TCP/IP network programming is covered in Part 8.
Having covered the development of a security policy in detail, let's put it all together and show how the security capabilities of an applet can be extended by adding permissions to the JDK security policy. Listing 3.5 contains the source code of the ExtendedApplet applet. This applet attempts to read the user.name system property, which is forbidden by the default JDK 1.2 security policy. The user.name property contains the current user's login name. The file extended.htm, shown in Listing 3.6, is an HTML file that is used to access ExtendedApplet.
import java.applet.*; import java.awt.*; import java.util.*; public class ExtendedApplet extends Applet { String text; int x = 30; int y = 120; public void init() { try { text=System.getProperty("user.name"); }catch(Exception ex){ text=ex.toString(); } } public void paint(Graphics g) { g.setFont(new Font("TimesRoman",Font.BOLD,12)); g.drawString(text,x,y); } }
<HTML> <HEAD> <TITLE>Extended Applet</TITLE> </HEAD> <BODY> <H1>An Applet that reads the user.name property.</H1> <APPLET CODE="ExtendedApplet.class" HEIGHT=300 WIDTH=600> Text displayed by browsers that are not Java-enabled. </APPLET> </BODY> </HTML>
NOTE: An introduction to applets and HTML is provided in Chapter 5, "JDK 1.2 Applet Writing Basics."
I've compiled ExtendedApplet.java and put the ExtendedApplet.class and extended.htm files on my Web server in the http://www.jaworski.com/ju/ path. You can run ExtendedApplet by opening the URL http://www.jaworski.com/ju/extended.htm with the appletviewer:
appletviewer http://www.jaworski.com/ju/extended.htm
Run appletviewer from a directory other than your ch03 directory to make sure that ExtendedApplet.class is loaded from my Web server and not your local file system. When you run appletviewer, the applet should display the exception shown in Figure 3.8.
FIGURE 3.8. The ExtendedApplet
generates an AccessControlException.
NOTE: If you use appletviewer to view ExtendedApplet from your local file system, appletviewer will not throw the access control exception.
Now, let's extend the capabilities of applets by adding the following permission to your java.policy file.
permission java.util.PropertyPermission "user.name", "read";
This file should be located in the C:\jdk1.2\lib\security directory. Modify the file as shown in Listing 3.7.
grant { // allows anyone to listen on un-privileged ports permission java.net.SocketPermission "localhost:1024-", "listen"; // "standard" properties that can be read by anyone permission java.util.PropertyPermission "java.version", "read"; permission java.util.PropertyPermission "java.vendor", "read"; permission java.util.PropertyPermission "java.vendor.url", "read"; permission java.util.PropertyPermission "java.class.version", "read"; permission java.util.PropertyPermission "os.name", "read"; permission java.util.PropertyPermission "os.version", "read"; permission java.util.PropertyPermission "os.arch", "read"; permission java.util.PropertyPermission "file.separator", "read"; permission java.util.PropertyPermission "path.separator", "read"; permission java.util.PropertyPermission "line.separator", "read"; permission java.util.PropertyPermission "user.name", "read"; };
Now that you've added the permission for all code to access the user.name property, rerun appletviewer http://www.jaworski.com/ju/extended.htm. The applet should now display your login name, as shown in Figure 3.9. Note that Figure 3.9 displays my login name, not yours. I suggest that you edit java.policy one more time to remove the permission to read the user.name property.
FIGURE 3.9. The ExtendedApplet displays my login name.
The previous section shows how to configure your security policy to give additional privileges to applets. You can also require applications to be governed by your security policy. In order to do this, you must run the applications under a SecureClassLoader. Normally, all classes that are loaded from the CLASSPATH are treated as system classes, are trusted, and not subject to the security policy. These classes are loaded with a null system ClassLoader.
JDK 1.2 allows local classes to be loaded from a second class path that is specified by the system property java.app.class.path. If you specify the second class path when you invoke the java interpreter, you can load an application using a SecureClassLoader that will require the application to abide by the security policy. For example, the following command loads MyApplication from the C:\untrusted directory using a SecureClassLoader object:
java -Djava.app.class.path="/untrusted" MyApplication
The SecureClassLoader object will ensure that MyApplication obeys the security policy that is in effect.
The JDK 1.1 expanded the security capabilities of JDK 1.02 to support application-level security. This new security support is provided by the Security API of the java.security packages and includes support for message digests, digital signatures, digital certificates, and key management. JDK 1.2 extended the capabilities provided by JDK 1.1 to include support for X.509 version 3 certificates and added new tools for working with certificates. The Java Cryptography Extension (JCE) is a separate add-on to the Security API that implements cryptographic algorithms that are subject to U.S. export controls. The JCE is available at http://www.javasoft.com.
The application-level security controls provided by the Security API can be used to protect information from unauthorized modification and disclosure as it traverses the Internet. They can also be used to authenticate the contents of messages and files and the identities of applications and individuals.
In this section you'll be introduced to the cryptographic capabilities provided by the Security API. Don't worry if you are unfamiliar with the basics of cryptography--they will be summarized in the next section.
NOTE: This section provides an overview of the cryptographic capabilities of the Security API. Chapter 8, "Applet Security," investigates the java.security packages and shows how to use the security-related tools of the JDK.
Cryptography is the study of algorithms and protocols for securing messages during transmission and storage. However, cryptographic techniques can be applied to other applications, such as identity verification and data authentication.
One of the most fundamental applications of cryptography is to disguise a message so that it can only be read by those who know how to recover the original message content. Encryption is the process of disguising a message, and decryption is the process of recovering the original message. An encrypted message is referred to as ciphertext, and an unencrypted or decrypted message is referred to as plaintext. Figure 3.10 provides an overview of these concepts.
FIGURE 3.10. \Encryption and decryption.
NOTE: Cryptographic techniques are oriented toward the protection of messages. However, these techniques can be used to protect other forms of data, such as files, database records, and Java byte codes.
Although a number of approaches to encryption have been developed over the years, most current encryption algorithms are based on the use of secret keys. A key is a sequence of binary digits that are used to encrypt or decrypt data. In key-based cryptography, the encryption and decryption algorithms are publicly known. Data is encrypted using one key and decrypted using another. Figure 3.11 provides an overview of key-based encryption. It is important that the decryption key be kept secret, or else anyone will be able to use it to decrypt messages.
FIGURE 3.11. Key-based encryption.
In some encryption algorithms, the encryption and decryption keys are the same, or the decryption key can be calculated from the encryption key within a useful time frame. These algorithms are known as secret-key algorithms or symmetric algorithms. Secret-key algorithms require the encryption key to be kept secret and require the sender and receiver to coordinate on the use of their secret keys. The Data Encryption Standard (DES) is an example of a secret-key algorithm.
Other encryption algorithms, known as public-key algorithms or asymmetric algorithms, are based on the use of separate encryption and decryption keys. Public-key algorithms require that it be computationally unfeasible to calculate the decryption key from the encryption key. Because of this requirement, the encryption key can be made public without affecting the security of the encryption algorithm. Figure 3.12 shows how public-key cryptography works.
FIGURE 3.12. Public key cryptography.
In public-key cryptosystems, each communicating entity (individual, organization,
software program, and so on) is assigned a public key and a private key. Entities
encrypt messages using the public key of the receiver. The receiver decrypts messages
using his, her, or its private key. The public key cannot be used to determine the
private key, so it does not need to be kept secret and can be openly published. Because
of this feature and others, public-key encryption is very popular in open communication
environments, such as the Internet. The RSA encryption algorithm is an example of
a public-key algorithm.
Cryptographic techniques are not limited to preserving the secrecy of messages. They are also used to maintain message integrity and to verify the authenticity of a message. One-way functions are used in these applications. A one-way function is one that is easy to compute, but computationally unfeasible to reverse. A real-life example of a one-way function is a shredding machine. It is easy to put a document in a shredder and produce paper strips, but it is very difficult to reverse the process. Message digest functions are also one-way functions. They compute values, referred to as message digests or hash values, that are used as fingerprints for messages. Good message digest functions have the following properties:
Figure 3.13 illustrates the use of message digest functions. Note that there is nothing secret about a message digest function--it is publicly available and uses no keys. The MD5 and MD4 algorithms are examples of message digest algorithms.
FIGURE 3.13. Message digest functions.
A digital signature is a value that is computed from a message using a secret key. It indicates that the person who holds the secret key has verified that the contents of the message are correct and authentic. Digital signatures often use public-key encryption algorithms with a slight twist--a private key is used for encryption, and a public key is used for decryption. This approach is often implemented as follows:
Signature generation:
Signature verification:
The preceding approach to signature generation/verification has the following features of real-world signatures, as well as other features that provide additional benefits:
Figure 3.14 summarizes the mechanics of using digital signatures. An example of a digital signature algorithm is the National Institute of Standards and Technology's (NIST) Digital Signature Algorithm (DSA).
FIGURE 3.14. Digital signatures.
Digital certificates, based on digital signatures, are messages signed by a certification authority that certify the value of an entity's public key. The X.509 certificates of the International Standards Organization are a popular digital certificate format. Figure 3.15 illustrates the use of digital certificates.
FIGURE 3.15. Digital certificates.
Central to the use of digital certificates is the notion of a certification authority (CA). A certification authority is an entity that is trusted to verify that other entities are who they claim to be and that they use a particular public key with a particular public-key encryption algorithm. To obtain a certificate from a CA, you usually have to submit documentation that proves your identity or that of your organization. For example, the certification process helps prevent unauthorized individuals from setting up business on the Web using the identity of Microsoft or Bank of America.
In a large networking environment, such as the Internet, multiple levels of CAs may be required. In this case a high-level CA, such as Verisign, Inc., the U.S. Post Office, or the National Security Agency, may provide certificates for second-level CAs. These second-level CAs may then provide certificates for other organizations. Individual companies may themselves act as a certification authority for their employees. A hierarchical certification structure, like that shown in Figure 3.16, is the result. Certification of an entity at the leaves and branches of this hierarchy depends on the certification of entities at higher levels within the hierarchy. These hierarchical certification relationships are referred to as certification chains.
FIGURE 3.16. Certification authorities form a tree-like hierarchy.
We'll study the use of digital certificates later in this chapter in the section "Using Certificates." Before we do that, we'll look at how the JDK 1.2 cryptographic architecture supports the topics discussed so far.
The Java Security API provides a flexible framework for implementing cryptographic functions and other security controls. It contains the hooks for message digest and digital signature computation, key generation and management, and certificate processing. It includes standard algorithms (such as MD5 and DSA) that support these security functions, but leaves out encryption algorithms (due to the restrictions of U.S. export controls). Instead of promoting a small set of cryptographic algorithms, the Java Security API implements an approach where different cryptographic packages may be provided by vendors and then be plugged in and installed within the common Security API framework.
The Provider class of java.security lays the foundation for using pluggable packages of cryptographic algorithms that support common functions such as message digest computation, digital signing, certificate processing, and key generation. The Provider class is a subclass of the Properties class of java.util. It encapsulates the notion of a cryptographic provider in terms of a provider name, version number, and information about the services provided by the provider.
The rationale for the Provider class is that it can be used to separate specific implementations of a cryptographic function (such as Company A's implementation of MD5, Company B's implementation of SHA-1, and Company C's implementation of MD5) from their provider-specific implementation. For example, several DSA packages may be available--some faster than others, some approved by the U.S. Department of Defense, and others supported by the Citizens Against Big Brother.
NOTE: The JDK 1.2 comes with a default provider, named "SUN", that includes an implementation of the MD5 and SHA-1 message digest algorithms, the DSA, and a DSA key generation capability.
The Security class provides a set of static methods that are used to manage providers. Providers are ranked in order of preference, with the most preferred provider receiving a rank of 1 and less preferred providers receiving a larger number. The methods of the Security class can be used to install providers, adjust their preference ranking, and retrieve information about the providers that are installed.
The Security API supports the notion of cryptographic engines. These engines are generic algorithm types--such as message digest, digital signature, and key generation--that support common cryptographic functions. The engines of the Security API include the MessageDigest, Signature, KeyPairGenerator, KeyFactory AlgorithmParameters and AlgorithmParameterGenerator classes.
The MessageDigest class, as you would expect, supports the computation of a message digest. The Signature class is an engine for calculating digital signatures based on provider-furnished digital signature algorithms. The Signature class supports both the creation of a digital signature and the verification of a digital signature.
The KeyPairGenerator class is an engine that provides a mechanism by which provider-furnished key generation algorithms may be accessed. Unlike MessageDigest and Signature, key generation is difficult to implement in an algorithm-independent manner. Because of this, KeyPairGenerator supports both algorithm-independent and algorithm-specific key generation--the difference being in the way that the algorithms are initialized. The KeyFactory class is used to translate algorithm-specific keys into objects that can be handled in a generic fashion.
The AlgorithmParameters class is used to manage the parameters of cryptographic algorithms, and the AlgorithmParameterGenerator class is used to generate parameters for algorithms. Specific implementations of the engine classes are provided by cryptographic package providers.
In addition to the engine classes described in the previous paragraphs, JDK 1.2 introduces the java.security.cert package to support the processing of digital certificates. The Certificate class provides an abstract class for managing certificates. It is extended by the X509Certificate, which supports X.509 certificate processing. The X509Extension interface is provided to support the extensions of X.509 version 3. The java.security.cert package also provides classes for working with revoked certificates.
X.509 identifies a particular format and content for digital certificates. This format has been popularized by Netscape's Secure Sockets Layer (SSL), the Java Archive (JAR) file format, and Privacy Enhanced Mail (PEM), as well as other emerging Internet security standards. X.509 certificates contain the following information:
The current version of X.509 is 3, although version 1 certificates are still in use. Version 3 added the capability to add custom extensions to certificates, such as email and IP addresses.
With respect to Java, the primary use for digital certificates is to support code authentication, as shown in Figure 3.17. Developers of Java code can digitally sign their code using their private keys. Users of the code verify the developers' signatures using the developer's public keys. Developers use digital certificates as a secure way to inform users of their public keys. Users manage developer certificates, identities, and public keys using the keytool, and establish developer-specific policies using the policytool.
FIGURE 3.17. Certificates are used to support code authentication.
NOTE: Chapter 8, "Applet Security," shows how to use the security-related tools of JDK 1.2.
In this chapter you studied the new security features of JDK 1.2. You learned how to specify the security policy for applets and applications and how to use permissions to implement fine-grain access controls. You learned a little bit about cryptography, delved into the JDK 1.2 cryptographic architecture, and covered the use of digital certificates. In the next chapter you'll explore the different ways in which Java can be used to develop software applications.
© Copyright 1998, Macmillan Computer Publishing. All rights reserved.