by David Baker and Mark Wutka
In any bulleted list description of the features of the Java execution environment, a phrase such as Java is secure will be found. Security can mean a lot of different things and, when developing Java applets, it is critical to understand the implications of Java security. Your applets are restricted to functioning within the Java security framework, which affects your design while enabling the safe execution of network-loaded code.
To ensure an uncompromised environment, the Java security model errs on the side of caution. All applets loaded over the network are assumed to be potentially hostile and are treated with appropriate caution. This fact will greatly restrict your design. To enable Java applets to expand beyond these limitations, the Java Security API has been developed.
In order to appreciate the intent and rationale behind the framework on which Java is based, you must investigate what makes security an issue at all. Java provides many solutions to matters of security, many of which will have ramifications on how you approach the installation and authoring of Java applications in your Internet network solutions.
The Internet forms a vast shared medium, allowing machines throughout the world to communicate freely. Trusted and untrusted computers, allowing access to millions of individuals with unknown intentions, are linked together. One computer may send information to almost any other on the Internet. Furthermore, Internet applications and protocols are not foolproof; at various levels, the identities can be concealed through a variety of techniques.
Adding Java to this scene opens up tremendous potential for abuse. Java's strengths present the most problematic issues. Specifically:
Given these characteristics, it is easy to see why Java code should be treated with great care. Without a tightly controlled environment, one could envision a number of problematic scenarios:
With these problems in mind, the overall problem can be seen. In order to be practical, Java must provide a controlled environment in which applications are executed. Avenues for abuse or unintended damage must be anticipated and blocked. System resources must be protected. To be safe, Java must assume code that is loaded over the network comes from an untrusted source; only those capabilities known to be secure should be permitted. However, Java should not be so restricted that its value goes unrealized.
For those who are familiar with Internet security systems, the issues Java faces are not new. This situation presents the old paradox where computers must have access to capabilities and resources in order to be useful. However, in an inverse relationship, the more power you provide to such systems, the greater the potential for abuse. In such a situation, a paranoid stance will render the system useless. A permissive stance will eventually spell doom. A prudent stance strikes to find an intelligent middle ground.
True to what the word means, Java provides a clear framework that creates a secure execution environment. Java is much more than a programming language. It consists of many different layers that create the Java execution environment:
At critical points within this structure, specific features ensure a safe execution environment. In isolation, each portion may provide little or no benefit to the system. In concert, these features work to create the solid and secure framework that makes Java a practical solution to executable content.
The Java language itself provides the first layer of network security. This security
provides the features that are necessary to protect data structures and limit the
likelihood of unintentionally flawed programs.
Java Enforced Adherence to the Object-Oriented Paradigm
No Pointer Arithmetic
NOTE: It is often said that Java does not contain pointers. In the abstract sense, pointers are merely variables that don't contain data, but rather identify the location of program data, data structures, or functions. References fit this definition. However, Java does not permit various operations that usually accompany pointers. Pointer arithmetic allows a program to reference and manipulate directly specific portions of machine memory that may not belong to the pointer's data structures. References may not do this.
Array-Bounds Checking
Java's Typecasting System
Language Support for Thread-Safe Programming
Final Classes and Methods The Java compiler converts Java code to a specific bytecode for the JVM. The compiler
ensures that all of the security features of the language are imposed. A trustworthy
compiler establishes that the code is safe and establishes that a programmer has
appropriated used typecasting.
Java bytecode is the essence of what is transmitted over the network. It is machine
code for the JVM. Java's security would be easy to subvert if only the policies defined
previously were assumed to have been enforced. A hostile compiler could be easily
written to create bytecode that would perform dangerous acts that the well-behaved
Java compiler would prevent. Thus, security checks on the browser-side are critical to maintaining a safe execution
environment. Bytecode cannot be assumed to be created from a benevolent compiler,
such as javac, within the JDK. Instead, a fail-safe stance assumes that
class files are hostile unless clearly proven otherwise. In order to prove such an assertion, when Java bytecode is loaded, it first enters
into a system known as the verifier. The verifier performs a number of checks
upon all class files loaded into the Java execution environment. The verifier goes
through a number of steps before approving any loaded code:
NOTE: For more detailed information on the verifier, read the paper by
Frank Yellin entitled "Low Level Security," available at http://java.sun.com/sfaq/verifier.html.
Bytecode that has reached this stage has been determined to be valid and then
enters the ClassLoader, an object that subclasses the abstract class java.lang.ClassLoader.
The ClassLoader loads applets incoming from the Net and subjects them to
the restrictions of the Applet Security Manager, described in "Part Five: Establishing
a Security Policy" later in this chapter. It strictly allocates namespaces for
classes that are loaded into the runtime system. A namespace is conceptual
real estate in which an object's data structures can reside. The ClassLoader ensures that objects don't intrude into each other's
namespaces in unauthorized fashions. Public fields and methods may be accessed, but
unless such an interface is defined, another object has no visibility to the variables.
This point is important because system resources are accessed through specific classes--ones
that are trusted to behave well and are installed within the JDK. If untrusted code
was able to manipulate the data of the core Java API, disastrous results would ensue. The ClassLoader also provides a strategic gateway for controlling which
class code can be accessed. For example, applets are prevented from overriding any
of the built-in Java classes, such as those that are provided within the Java API.
Imported classes are prevented from impersonating built-in classes that are allowed
to perform important system-related tasks. When a reference to an object is accessed,
the namespace of built-in classes is checked first, thwarting any spoofing by network-loaded
classes.
The previous pieces of the Java security framework ensure that the Java system
is not subverted by invalid code or a hostile compiler. Basically, they ensure that
Java code plays by the rules. Given such an assurance, you are now able to
establish a higher-level security policy. This security policy exists at the application
level, allowing you to dictate what resources a Java program can access and manipulate. The Java API provides the java.lang.SecurityManager class as a means
of creating a clearly defined set of tasks an application can and cannot perform,
such as access files or network resources. Java applications don't start out with
a SecurityManager, meaning that all resources it could restrict are freely
available. However, by implementing a SecurityManager, you can add a significant
measure of protection. Java-enabled browsers use the SecurityManager to establish a security
policy that greatly distinguishes what Java applets and Java applications can do.
Later, in the section "The SecurityManager Class," such special
restrictions are described in detail.
Figure 36.1 illustrates how these separate pieces of the framework interlock to
provide a safe, secure environment. This careful structure establishes an intelligent,
fail-safe stance for the execution of Java programs:
FIG. 36.1
Java applets are programs that extend the java.applet.Applet class. They
can be seamlessly downloaded and executed by a Java-enabled browser, such as HotJava
or Netscape. Prior to the JDK 1.1, there was no mechanism for establishing proof
of ownership and trust of authorship. Thus, all applets must be assumed to be from
an untrustworthy source.
An important point to realize when investigating Java security is the distinction
between Java applets and Java applications. Applets are special programs that extend
the Applet class. They can be dynamically executed within a browser merely
by loading an HTML page that contains an APPLET element. Applications, on the other hand, are executed directly by the Java interpreter.
They must be manually installed on the local system and consciously executed by the
user on that system. A Java browser does not execute these programs. See "Developing a Java Application," Chapter
21 Because of the differences between applets and applications, the two are allowed
to execute under different security policies. It is assumed that during the manual
installation process, the user has approved of the application's potential access
to system resources. The application is trusted to the degree that it can open and
write files, connect to various network resources, and execute a variety of programs
on the local system. Such a policy is consistent with just about any other application
that you would install on your personal computer. Applets, on the other hand, are assumed to come from an untrusted source and could
potentially perform harmful acts unless run within a carefully controlled execution
environment.
Most of the security features that are added to Java applets are imposed by the
class java.lang.SecurityManager, although (as previously mentioned), the
use of a ClassLoader instance plays a significant role as well. The SecurityManager
class allows you to establish a specific security policy that is appropriate to the
level of trust given to a particular program. This abstract class provides the ability
to create an object that determines whether an operation that a program intends to
perform is permitted. The SecurityManager has methods for performing the following acts to
enforce a security policy:
Within the HotJava and Netscape browsers, a specific policy has been identified
for the loading of untrusted applets. The SecurityManager performs a number
of checks on a program's allowed actions, while the ClassLoader, which loads
Java classes over the network, ensures that classes loaded from external systems
do not subvert this security stance.
For example, assuming that the applet was downloaded from www.trusted.org,
the following code will fail in an applet:
Part Two: The Java Compiler
Part Three: The Verifier
Part Four: The ClassLoader
Part Five: Establishing a Security Policy
Putting It All Together
A safe environment is created by different pieces working in a smooth fashion.Applet Restrictions
Applets versus Applications
The SecurityManager Class
The Security Policy of Java Browsers
FileInputStream readIn = new FileInputStream(readFile);
FileOutputStream out = new FileOutputStream(writeData);
out.write(1);
File oldName = new File("one.txt"); // Can't modify files,such as
File newName = new File("two.txt"); // by changing their names
oldName.renameTo(newName); // within directories.
File removeFile = new File("import.dat"); // Can't delete files.
removeFile.delete();
isHere.exists();
createDir.mkdir();
File lookAtDir = new File("/users/hisdir");
fileNames = lookAtDir.list();
long checkSize;
boolean checkType;
long checkModTime;
checkSize = checkFile.length();
checkType = checkFile.isFile();
checkModTime = checkFile.lastModified();
// Can't open TCP socket.
Socket mailSocket = new Socket("mail.untrusted.org",25);
// The URL objects are similarly restricted.
URL untrustedWeb = new URL("http://www.untrusted.org/");
URLConnection agent = untrustedWeb.openConnection();
agent.connect();
// As are UDP datagrams.
InetAddress thatSite = new InetAddress("www.untrusted.org");
int thatPort = 7;
byte[] data = new byte[100];
DatagramPacket sendPacket =
new DatagramPacket(data,data.length,thatSite,thatPort);
DatagramSocket sendSocket = new DatagramSocket();
sendSocket.send(sendPacket);
NOTE: As described later, however, it will be shown that this network connection
restriction was not implemented completely. This deficiency has since been corrected.
listener.accept();
Runtime systemCommands = Runtime.getRuntime();
systemCommands.exec(command);
systemCommands.loadLibrary("local.dll");
Runtime systemCommands = Runtime.getRuntime();
systemCommands.exit(0);
// As does this mechanism.
System.exit(0);
Property
Purpose
Accessible Key to Applets?
file.separator
The token used to separate files and directories on the filesystem (for example,
"/" on UNIX and "\" on Windows NT/95).
yes
java.class.path
The CLASSPATH value used to search for classes to load.
no
java.class.version
The version of the Java API used.
yes
java.home
The directory in which theJava environment is installed.
no
java.vendor
A vendor-specific string used for identification purposes.
yes
java.vendor.url
URL of a resource identifying the vendor
yes
java.version
Version number of the Java interpreter.
yes
line.separator
The character(s) that separate lines on the system (for example, the linefeed character
on UNIX, or a linefeed, carriage-return pair on Windows NT/95).
yes
os.arch
The operating system's hardware architecture.
yes
os.name
The name of the operating system.
yes
os.version
Operating system version.
yes
path.separator
The token used to separate directories in a search- path specification (for example,
":" on UNIX and ";" on Windows NT/95).
yes
user.dir
The current working directory.
no
user.home
The user's home directory.
no
user.name
The account name of the user
no
As you might imagine, this policy presents a number of severe limitations that affect what your applets can and cannot do. One particular problem is that the Internet, by its very nature, is a distributed system. However, Java applets are prevented from accessing this Web of computers--they can only connect to the machine from which they were downloaded.
Furthermore, because data cannot be written to the local system, applets cannot maintain a persistent state across executions on the client. As a work-around, applets must connect to a server to store state information, reloading that information from the original server when executed at a later time.
HotJava has a properties file that allows for certain of the previous restrictions to be relaxed for all applets. The HotJava User's Guide provides more information on this process. More importantly, the new Java API provides the framework for creating specialized security policies for trusted applets loaded from known sources. This latter solution is described later within this chapter.
Despite its success and significant attention, Java is still a very immature system. Since the release of the 1.0 JDK, a number of practical flaws have been identified. Understanding these flaws will provide you with a feel for the medium into which you are immersing yourself.
An important point to note in this regard is the degree of openness that has been encouraged within the Java development arena. Obviously, companies such as Sun and others, that have a significant stake in promoting Java, suffer when a bug or flaw is revealed. Nevertheless, public scrutiny and critiques have been encouraged and generally well-received.
Based on the experience of most security professionals, such public review is an essential component of the development of a secure system. In most cases, it is impossible to prove a system is secure. A safe stance is to assume that a system with no known flaws is merely one with flaws that are waiting to be exposed and exploited. Peer review allows for various experts to search for these hidden flaws--a process that is very familiar within the Internet community. Java's evolution has followed this philosophy and, from most practical observations, it appears that everyone has benefited.
The opposing argument is that exposing the implementation of the system to the public allows untrusted and malicious individuals to identify and act on flaws before others can rectify the situation; by keeping a system secret, it is less likely that abusive hackers will discover these problems. Many experienced with Internet security disagree, believing that obscuring the implementation is unwise: Secrecy in design creates a system that is ultimately poorer, while providing more opportunity for malevolence.
CAUTION:
A word to the wise: Always treat with caution any supposedly secure system whose designer claims that the system's security would be subverted by revealing the details of its implementation.
During the first few months after the release of the Java Development Kit, a number of problematic issues were revealed. The following list is an overview of some of the flaws discovered in Java since its release:
Of the three mentioned flaws, the DNS attack identified first has received perhaps the most public attention. The basic problem lies within the enforcement of the security policy by the SecurityManager.
The applet policy enforced by Netscape and HotJava dictates that a network connection can only be opened by the applet to the machine from which it was downloaded. As indicated in Chapter 23, network computers identify each other on the Internet with IP addresses. The Domain Name System allows IP addresses to be associated in various ways, primarily enabling the use of human-understandable host names.
See "Internet Protocol (IP)," Chapter 21
In the flawed SecurityManager, the IP address of the incoming applet would be used to look up the host name of the remote machine. Then this host name would be used to look up the set of IP addresses to which it is mapped. Such a lookup should return at least the original IP address, but it may contain other IP addresses; such IP addresses may correspond to the same physical machine or completely separate machines.
Such a system might allow some flexibility in designing applets, allowing machines that share the same host name to spread out the responsibility for handling connections initiated from downloaded applets. However, such a system subtly violates the original security policy in a very significant way.
The DNS is a distributed resource. Various systems throughout the Internet are responsible for maintaining the integrity of specific parts. You have no ability to guarantee that a specific DNS server will not be broken into by hackers, and malicious individuals could easily set up their own DNS servers providing information that could exploit this leniency in the SecurityManager.
By design, the DNS is insecure. One could claim that Java should not be to blame for the limitations of such a commonly used system. This nature of the DNS is well-known to Internet security specialists, however, and it should have been anticipated.
One final point should be made about the problems found with the Java security system. The design of the system appears inherently sound. Rather, it is the implementation of that design which is not completely flawless, and such is to be expected of any technology as new as Java.
The term denial-of-service is a standard way of describing a particular type of security attack. Such attacks are aimed at preventing you or anyone else from using your own computer, rather than attempting to obtain sensitive data from your systems. These attacks often utilize "brute force" to overload a system.
Denial-of-service attacks in areas other than Java include such factors as:
Most of these attacks exploit a resource's own usefulness to make the system effectively useless. Because of this, it's not completely practical or possible to completely prevent such attacks. Only by removing the features that make the system useful can it be protected.
Denial-of-service attacks are quite possible with Java applets. These attacks don't require much imagination:
Currently, these types of attacks are identified as out of the scope of the Java security model. Java must continue to be useful. If applets have interesting and powerful capabilities, they could potentially exhaust the practical limitations of your computer. However, Sun continues to investigate the feasibility of controlling more closely the amount of system resources an applet can use.
TIP: To see what a denial-of-service attack through Java might look like, check out the following resource that collects such hostile applets for demonstrations: http://www.math.gatech.edu/~mladue/HostileApplets.html
By now, you have come to realize the significant, though prudent, limitations to which Java applets are held. These policies create a safe but restricted environment. When designing an applet to accomplish certain tasks, cumbersome work-arounds must be created, while other goals just can't be accomplished through applets.
This situation is necessary because all applets are treated as hostile--a fail-safe stance. In many situations, however, you are able to assert that certain programs are not hostile. For instance, applets distributed by a faithful vendor or provided from within your firewall may be reasonably expected to have greater access to system resources than a random applet loaded from someone's Web page.
One of the key capabilities missing from the initial Java implementations was the ability to establish trust relationships. With Java 1.1, and the formation of the Java Security API, you have the ability to create these relationships and verify that code from these sources is not altered by an outside party.
The features of the Java Security API are based on computer cryptography designs and algorithms. A quick investigation of these concepts can help you understand how the Security API works.
The cryptographic scheme that is most familiar to many is symmetric cryptography, or private-key encryption. The concept is that a special formula or process takes a piece of data and uses a special key, such as a password, to produce an encrypted block of data.
Given only the encrypted data, or ciphertext, it is difficult or impossible to reproduce the original copy. With the key, however, you can decrypt the ciphertext into the original message.
Thus, anyone with access to the key can easily decrypt the data. Because the security
of this system depends on the secrecy of this key, this scheme is referred to as
private key encryption. It is symmetrical in nature because the same key that
is used to encrypt the data is required to decrypt the message. Figure 36.2 illustrates
the private key encryption scheme.
FIG. 36.2
Private key cryptography uses the same key for encryption and decryption. To be secure,
the key must be kept secret.
A number of cryptographic systems use private key cryptography. Data Encryption Standard (DES) is a widely used system; however, cracking it is practical with today's technology. IDEA is a much newer algorithm and is believed to be much more secure than DES, although it has not been as thoroughly tested as DES. RC2 and RC4 are propriety algorithms distributed by RSA Data Security.
One of the problems with using private key encryption to protect communications is that both parties must have the same key. However, this exchange of private keys must be protected. Thus, in order to securely transmit documents, a secure mechanism of exchanging information must already exist.
NOTE: While private key cryptography is not available as part of the Java 1.1 release, it is important to understand how it works. Future releases of Java will include private key cryptography.
Public key cryptography is a phenomenal idea. It is a radical system that is based on breakthroughs made during the 1970s. The concept is based on special mathematical algorithms.
A special formula is used to create two keys that are mathematically related, but neither can be induced from the other. One key is used to encrypt a particular message to produce a ciphertext. The other key is used to decrypt the message; however, the original key cannot be used to decrypt the ciphertext. Thus, this type of cryptography is referred to as asymmetric.
This system solves the problem of key distribution that limits private key cryptography.
An individual who expects to receive protected documents can advertise one of the
keys, generally referred to as the public key. Anyone who wishes to send an
encrypted message to this person merely picks up the public key and creates the ciphertext.
This encrypted message can be safely transmitted because only the other key can decrypt
it. The recipient keeps the corresponding, or secret, key hidden from others
because it is the only key that can be used to read messages encrypted by the public
key. Figure 36.3 shows this mechanism.
FIG. 36.3
Public key cryptography provides a solution to key distribution.
Perhaps of more usefulness to Java applets, however, is the converse operation that is known as signing. Given a message, the secret key is used to create an encrypted signature. The unencoded message is transmitted along with the signature and, if the message is altered, the signature cannot be decrypted. Anyone who receives the message can obtain the freely available public key to ensure two things:
The process of signing messages through public key cryptography is shown in Figure
36.4.
FIG. 36.4
Digital signatures can establish identity and data integrity.
One of the limitations in the public key system is verifying that a public key truly belongs to the individual you believe it does. It is conceivable that a hostile individual could send you a message signed with a secret key, claiming to be from another party. This attacker then advertises a public key as belonging to the impersonated person. You retrieve this key and decrypt the signature. Believing that you have verified the author, you now trust information that, unbeknownst to you, is written by a hostile source.
Secure transmission systems on the Web have turned to a system known as Certification Authorities (CA) to overcome this limitation. Basically, a CA is an organization or company that is very well-known and goes to great lengths to ensure that its public key is properly advertised. The CA then signs the key of other agencies that conclusively prove their identity. When you receive the public key of this agency, you can use the CA's public key to verify it. If successful, you know that the CA believes this agency is what it claims to be. Thus, the CA certifies the agency.
If your Web browser implements a mechanism of secure communications, such as SSL, you can see a list of some certificate authorities. Netscape is SSL-enabled--if you choose Options, Security Preferences, Site Certificates, you can see the certificates of the CAs distributed with the browser.
After this lengthy discussion, you might be wondering why encryption can expand the capa- bilities of applets. As mentioned before, applets are assumed to be untrusted and potentially hostile. However, if an applet was digitally signed with public key cryptography, you could identify the company that created the applet and ensure that a hacker has not somehow altered what the company claims to have written.
Now you can establish trust relationships. You can assign specific roles to applets from known agents. For instance, you may purchase a stock quote service from a company. To use that service you download an applet. Because you already have a relationship with that company and you want to trust the information it provides, you can feel comfortable in allowing the applet greater access to your local system:
It is important to note that other parts of the Java security framework are still in place. The bytecode is still verified to ensure validity. Furthermore, this isn't an all-or-nothing proposition. Applets from trusted sources may be given incrementally greater access to your computer. (Review the various checks the SecurityManager class has available to get a feel for the gradations of increased access that could be allowed.) Finally, unsigned applets are still untrusted; they will still be subject to the same limitations that were in place prior to the release of the Java Security API.
Key management is an extremely important aspect of security. You must keep your database of certificates up-to-date and keep your private keys secret. If you keep keys and certificates in separate files scattered around your system, you may accidentally place a private key in a public directory where someone could steal it. To help you with key management, Java 1.1 includes a key database and a key management tool called javakey.
The javakey program doesn't actually manage keys, it manages entities called signers and identities, each of which have keys associated with them. A signer is a person or organization that is able to digitally sign information. Since signing requires a private key, a signer has both a public key and a private key, as well as a certificate authenticating the public key.
An identity is a person or organization that has one or more public keys and certificates verifying the public keys. Generally, an identity is an entity from which you receive digitally signed code. A signer is an entity that creates a digital signature. If you are not signing any code, you might never have any signers in your key database.
In order to store keys for a signer or identity, you must first create an entry in the key database. When you create the entry, you must give the entity a name and also indicate whether the entity is trusted. The following command creates an entry for a signer named mark who is not considered trusted:
javakey -cs mark false
The -cs option indicates that you are creating an entry for a signer. A -c option indicates that you are creating an entry for an identity. The false keyword indicates that the entity is not trusted (which is the default). Use true to indicate that the entity is trusted. The following command creates an entry for a trusted identity named verisign:
javakey -c verisign true
You will need at least one certificate from a trusted entity before you will be able to verify certificates. Verisign, for instance, is a company that creates digital certificates for other companies, once it verifies the identity of the other company. Once you have Verisign's certificate in your database, you can use it to validate certificates signed by Verisign.
Once you have created an entry for an entity, you can add keys and certificates for that entity. For example, suppose you received Verisign's public key in a file called vskey.key and the certificate for that key in a file called vskey.cer. Use the -ik flag on the javakey command to import the public key into the key database:
javakey -ik verisign vskey.key
Next, use the -ic option to import the certificate:
javakey -ic verisign vskey.cer
If you have a public/private key pair for a signer, you can import the pair with the -ikp file. If entity mark's public key is in the file markpub.key and the private key is in the file markpvt.key, you can import the keys with the command:
javakey -ikp mark markpub.key markpvt.key
You can list the entities in the database with the -l option:
javakey -l
You can also get more detailed information about a particular signer with the -li option. For example, to get more information about mark, use the command:
javakey -li mark
To remove an entity, use the -r option:
javakey -r mark
If you want to digitally sign your code, you need a private key and its accompanying public key. The easiest way to create this key pair is with javakey. The following commands create a trusted signer entity named trustme and a public/private key pair for trustme:
javakey -cs trustme true javakey -gk trustme DSA 512
The key pair is generated using the Digital Signature Algorithm (DSA) and each key contains 512 bits. Once you create a key pair for trustme, you can generate a certificate for trustme. Certificates that you generate yourself are generally only useful for testing or for use within your own company. Under normal circumstances, you generate a key pair and send your public key to a Certificate Authority (CA) like Verisign. The CA verifies your identity and digitally signs your public key, creating a certificate that you can then send to someone else.
In order to create a certificate, you must first create a file containing information about the issuer and the subject of the certificate. Listing 36.1 shows a certificate directive file for trustme signing its own public key.
issuer.name=trustme issuer.real.name=Trust Me issuer.org.unit=Certificate Dept. issuer.org=Trust Me, Inc. issuer.country=USA subject.name=trustme subject.real.name=Trust Me subject.org.unit=Certificate Dept. subject.org=Trust Me, Inc. subject.country=USA start.date=17 Jan 1997 end.date=28 Feb 1997 serial.number=1001 out.file=cert.cer
The -gc option on javakey generates a certificate from a directive file. The following command generates a certificate using a directive file named certinfo:
javakey -gc certinfo
When you store your applet or application in a JAR file, you can digitally sign the JAR file. If an applet is loaded from a JAR file signed by a trusted entity, the applet is not subject to the usual security restrictions. In future releases of Java, you will be able to configure what a signed applet can do, even assigning permissions based on who signed the applet.
In order to sign a JAR file, you must create a signature directive file containing instructions about who is signing the file and which certificate file to use. Listing 36.2 shows a simple directive file for a signer entity named trustme:
signer=trustme cert=1 chain=0 signature.file=TRUST
The signer field indicates which entity in the key database is signing a file. The cert field indicates which of the signer's certificates to use. The certificates are numbered starting at 1, and unless a signer has more than one certificate, this number will always be 1. At some point in the future, Java will support certificate chaining, where a certificate contains a nesting of certificates. When this feature is supported, the chain field will specify the nesting depth to include in the signature. For now, leave this field at 0. The signature.file field indicates the filename for the signature when it is placed in the JAR file.
In order to maintain backwards compatibility with existing formats, namely the .ZIP format, JAR files do not contain a separate format for digital signatures. Instead, a JAR file contains a directory named META-INF, which contains the digital signatures for files in the archive. In the previous signature directive file, the signature.file field of TRUST would generate files named TRUST.SF and TRUST.DSA in the META-INF directory of a JAR file.
The -gs option in the javakey command digitally signs a JAR file. The following command digitally signs a JAR file named TrustedApplet.jar using a signature directive file named signtrust:
javakey -gs signtrust TrustedApplet.jar
The resulting JAR file is named TrustedApplet.jar.sig. Listing 36.3 shows the contents of the signed JAR file, using the -t option in the JAR command.
META-INF/MANIFEST.MF META-INF/TRUST.SF META-INF/TRUST.DSA TrustedApplet.class
The Security API in Java 1.1 is focused on providing support for digitally signed applets. There is some level of support for generating and checking digital signatures from a program, and future versions will provide classes for encrypting and decrypting information.
The Security API exists for two reasons--to allow your programs to perform security functions, and to allow manufacturers of security software to create their own security provider services. Java 1.1 ships with a single security provider, which is simply called "Sun." Other vendors may provide their own security services. For instance, future versions of Netscape may include a Netscape security provider. These providers may provide additional services beyond those defined in the Java 1.1 Security API.
The Security API revolves around the manipulation of keys. As you might guess, there are classes defined for both public and private keys. Since these keys share many common features, they both derive from a common superclass called Key. The three important features of a key are its algorithm, its format, and the encoded key value. You can retrieve these values from any key using the following methods:
public final String getAlgorithm() public final String getFormat() public final byte[] getEncoded()
NOTE: Actually, the getEncoded method is defined as protected in the Key class, and is redefined as a public method in the PublicKey class. This allows you to see the key value in public keys, but hides the value in private keys.
The constructors for the Key class are:
public Key(byte encodedValue[], String format) public Key(byte encodedValuep[, String format, String algorithm)
These constructors are also implemented by the PublicKey and PrivateKey classes. In addition, the PrivateKey class has a constructor that takes an encoded value that is assumed to be in PKCS#8 format:
public PrivateKey(byte encodedValue[])
Similarly, the PublicKey class has a constructor that takes an encoded value that is assumed to be in X.509 format:
public PublicKey(byte encodedValue[])
Since keys are often used as a private/public key pair, the KeyPair class provides a way to associate two keys together. The KeyPair class has only one constructor, which takes a public and a private key:
public KeyPair(PublicKey public, PrivateKey private)
The getPrivate and getPublic methods in KeyPair return the private and public keys, respectively:
public PrivateKey getPrivate() public PublicKey getPublic()
The Signature class performs two different roles--it can digitally sign a sequence of bytes, or it can verify the signature of a sequence of bytes. Before you can perform either of these functions, you must create an instance of a Signature class. The constructor for the Signature class is protected. The public method for creating signatures is called getInstance and takes the name of the security algorithm and the name of the provider as arguments:
public static Signature getInstance(String algorithm, String provider)
For the default package provided with Java 1.1, the most common call to getInstance is:
Signature sig = Signature.getInstance("DSA", "SUN")
If you are creating a digital signature, call the initSign method in Signature with the private key you are using to create the signature:
public final void initSign(PrivateKey key)
If you are verifying a signature, call initVerify with the public key you are verifying against:
public final void initVerify(PublicKey key)
Whether you are creating a signature, or verifying one, you must give the Signature class the sequence of bytes you are concerned with. For instance, if you are digitally signing a file, you must read all the bytes from the file and pass them to the Signature class. The update method allows you to pass data bytes to the Signature class:
public final void update(byte b) public final void update(byte[] b)
The update methods are additive, that is, each call to update adds to the existing array of bytes that will be signed or verified. The following code fragment reads bytes from a file and stores them in a Signature object:
Signature sig = new Signature("DSA"); sig.initSign(somePrivateKey); FileInputStream infile = new FileInputStream("SignMe"); int i; while ((i = infile.read()) >= 0) { sig.update(i); } byte signature[] = sig.sign(); // Do the signing
Once you have stored the bytes in the Signature, use the sign method to digitally sign them, or verify to verify them:
public final byte[] sign() public final boolean verify(byte[] otherSignature)
As you already know, there are two types of entities stored in the key database--identities (public keys only) and signers (public/private key pairs). The Identity class represents an identity, while the Signer class represents a signer. These two classes are abstract classes; you cannot create your own instances. Instead, you must go through your security provider to create and locate these classes.
Once you have an instance of an Identity, you can retrieve its public key with getPublicKey or set its public key with setPublicKey:
public PublicKey getPublicKey() public void setPublicKey(PublicKey newKey)
In addition, you can retrieve all the identity's certificates using the certificates method:
public Certificate[] certificates()
You can add and remove certificates with addCertificate and removeCertificate:
public void addCertificate(Certificate cert) public void removeCertificate(Certificate cert)
The Signer class is a subclass of Identity, and adds methods for retrieving the private key and setting the key pair:
protected PrivateKey getPrivateKey() protected final void setKeyPair(KeyPair pair)
A certificate is little more than a digitally signed public key. It also contains the owner of the key, and the signer. The owner and the signer are called principals, and are generally entities that are stored in the key database. You can retrieve the public key from a certificate with getPublicKey:
public abstract PublicKey getPublicKey()
You can also retrieve the principals from a certificate. The Guarantor is the entity who is signing the public key (guaranteeing its authenticity), and the Principal is the owner of the key that is being guaranteed:
public abstract Principal getPrincipal() public abstract Principal getGuarantor()
The only interesting method in the Principal interface is getName which returns the name of the principal:
public abstract String getName()
The IdentityScope class represents a set of identities. Generally, this class represents the identities in the key database. Once you have an instance of an IdentityScope, you can add entities, remove entities, and find entities. The getSystemScope method returns the default identity scope for the security system:
public static IdentityScope getSystemScope()
You can locate identities either by name, public key, or using a Principal reference:
public Identity getIdentity(String name) public Identity getIdentity(PublicKey key) public Identity getIdentity(Principal principal)
The identities method returns an enumeration that allows you to enumerate through all the identities in the scope:
public abstract Enumeration identities()
The addIdentity and removeIdentity methods allow you to add new identities to the scope, or to remove old ones:
public abstract void addIdentity(Identity id) public abstract void removeIdentity(Identity id)
Listing 36.4 shows an example program that creates a digital signature for a file and writes the signature to a separate file.
import java.security.*; import java.io.*; import java.util.*; public class SignFile extends Object { public static void main(String[] args) { try { // Get the default identity scope IdentityScope scope = IdentityScope.getSystemScope(); // Locate the entity named trustme Identity identity = scope.getIdentity("trustme"); // Create a signature and initialize it for creating a signature Signature sig = Signature.getInstance("DSA", "SUN"); Signer signer = (Signer) identity; sig.initSign(signer.getPrivateKey()); // Open the file that will be signed FileInputStream infile = new FileInputStream( "SignFile.java"); // Read the bytes from the file and add them to the signature int i; while ((i = infile.read()) >= 0) { sig.update((byte)i); } infile.close(); // Open the file that will receive the digital signature of the // input file FileOutputStream outfile = new FileOutputStream( "SignFile.sig"); // Generate and write the signature outfile.write(sig.sign()); outfile.close(); } catch (Exception e) { e.printStackTrace(); } } }
The ability to generate digital signatures and verify them from a program allows you to provide new levels of security in your programs. This is especially useful in the area of electronic commerce where you can now digitally sign orders and receipts.