Java security evolution and concepts, Part 2

Discover the ins and outs of Java security

One of Java's main features is its ability to move code over a network and run that code. Unlike other languages, Java has been designed to do this securely. Java security has evolved over time; recent releases provide fine-grained security features that enable implementation of a flexible policy decoupled from the implementation mechanism.

Java security evolution and concepts: Read the whole series!

This article, Part 2 of a series, will cover the various aspects of securely running Java code downloaded from a network. Although mobile code is not a revolutionary concept, Java and the Internet present some unique challenges to computer security. The evolution of the Java architecture and its impact on security, different security APIs and tools, and applet security will be covered in this series.

This is not intended to be a comprehensive guide to computer security, a multifaceted issue encompassing several disciplines, departments, and cultures. Investments in security technologies should be followed by investments in personnel training, strict enforcement of policies, and periodic review of the overall policy.

Language vis-a-vis runtime security

Although enforcement of policies during code execution is a substantial part of security, proper security starts at the very beginning, during the generation of byte code. A language's type safety, which is enforced by the compiler and checked by the runtime environment, proves critical to an overall secure environment. Many of computing's earliest security breaches stemmed from the ability to easily overflow buffers or access memory unimpeded, situations caused in part by a language's poor type safety and inadequate enforcement in the executing environment.

Java security manifests itself in the following forms: protection built into the language, building blocks for a flexible secure environment, and protection against accidental or malicious attacks to the language and platform. Simply put, Java security should ensure that the specifications for the language and virtual machine (VM) are followed.

Despite the safety checks enforced by the compiler, the VM must still be able to deal with faulty byte code, whether generated accidentally or maliciously. Below, we'll examine the enforcement of runtime checks during execution and the evolution of the runtime environment to support the design of a very flexible security policy.

As for language security inherent in the language's design, Java's design was heavily influenced by C and C++ -- particularly their weaknesses. Consequently, the Java compiler generates warnings for uninitialized variables. The language itself is strongly typed, with many unsafe constructs omitted or modified; for example, array accesses are done with index checking. Moreover, because memory deallocation in Java is the garbage collector's responsibility rather than the programmer's, Java avoids many common programming errors in C and C++ caused by faulty memory deallocation. Finally, the compiler enforces exception-catching. This discipline of catching and fixing potential errors may not have direct security implications. However, an unhandled error might lead to unpredictable behavior, which, from a security standpoint, should be avoided.

Evolution of Java security

Some security features touted in recent Java releases were also available in earlier versions. However, using them involved a considerable amount of additional programming and a good understanding of the security model. Generally, Java has provided access to the language and the runtime environment conservatively, which is good from a security perspective, although somewhat restrictive of the ability to support different policies.

It's necessary for a security designer to understand Java security evolution, as many enterprises may have to support pre-Java-2 releases for years to come. So this article will concentrate on Java 2 Security, but only after an overview of security in previous releases.

JDK 1.0 security -- Java security hits the sandbox

JDK 1.0 featured the sandbox security model, as seen in Figure 1. The sandbox model confines Java applets, potentially dangerous or not, to a strictly defined arena where they cannot affect other system resources. (For more details of the sandbox model, including its limitations, see the "Sidebar 1: Sandbox model for security.")

Figure 1. JDK 1.0 security model -- the sandbox Click on thumbnail to view full image (20 KB)

Since applications load locally, they, unlike applets, need not be deemed untrustworthy and enjoyed unlimited access to all resources in JDK 1.0. A consistent security policy for applets and applications was therefore not supported by this model.

JDK 1.1 Security -- all or nothing

JDK 1.0's sandbox model provided virtually no flexibility. In an attempt to overcome this, JDK 1.1 introduced the signed applet, illustrated in Figure 2. A signed applet is an applet packaged as a Java Archive (JAR) file and signed with a private key. The signed applet enjoys unlimited access, just like a local application, provided the corresponding public key is trusted in the executing environment. Unsigned applets default back to the sandbox model.

Figure 2. The JDK 1.1 security model Click on thumbnail to view full image (24 KB)

JDK 1.1's security model was less restrictive than the sandbox model and provided for slightly more consistent treatment of applets and applications. However, it had one major disadvantage: applets either received unlimited access or were confined to the sandbox -- there was no option for selective access to resources. This model was another example of an inflexible implementation where the policy was forced by the mechanism.

Java 2 Security -- fine-grained security

The Java 2 Security model, as illustrated in Figure 3, provides for a consistent and flexible policy for applets and applications. While applications still run unrestricted by default, they can be subjected to the same policy as applets.

The Java 2 model also introduces the concept of a ProtectionDomain, which permits a highly flexible security policy decoupled from its implementation.

Figure 3. Java 2 Security model Click on thumbnail to view full image (28 KB)

Overview of Java 2 security

The various features of the Java 2 Runtime Environment's security model can be seen in Figure 4. As a general caveat, some of the issues discussed might not be part of the specification per se, but still peculiar to most implementations.

Figure 4. Java 2 security architecture

Byte-code verifier

Since Java code can be imported from anywhere in the network, it is critical to screen the code to be sure that it was produced by a trustworthy compiler. The byte-code verifier, sometimes referred to as a mini-theorem prover, tries to prove that a given series of Java byte codes are legal.

Among other things, it verifies the format of the class file - it must have the right length, the correct magic numbers, no operand stack overflows and underflows, and so on. In short, the verifier confirms or denies that the class file is consistent with the specifications. Although Figure 4 appears to indicate that byte-code verification occurs when the class is loaded, some of these checks may be delayed until just before the byte code is first executed.

Even though the byte-code verifier serves an important purpose, it is not very interesting from a programming standpoint because its behavior cannot be altered programmatically. The behavior may be altered with command line options on the interpreter, when applicable.

ClassLoader

The ClassLoader, which loads Java byte codes into the JVM, is an important link in the security chain. It works in conjunction with the SecurityManager and the access controller to enforce security rules. Notice in Figure 4 that the ClassLoader is involved in enforcing some security decisions earlier in an object's lifetime than the security manager. Also, information about the URL from which the code originated and the code's signers is initially available to the ClassLoader.

It is possible to implement a customized ClassLoader or a subclass from java.security.SecureClassLoader to provide security features beyond those offered by the standard Java 2 Security model.

The ClassLoader, as its name indicates, loads classes into the VM. It is also responsible for the concept of namespaces at runtime, which are created by packages. With namespaces, identically named identifiers can refer to different objects.

Since a ClassLoader is itself a class, some bootstrapping is necessary early on. The ClassLoader, referred to as the primordial class loader and usually written in a native language, loads the bootstrap classes in a platform-dependent manner.

Some classes defined in the java.* package are essential to the JVM and the runtime system. These are referred to as system classes, loaded by the System ClassLoader. The terminology sometimes extends to all classes found in the CLASSPATH environment variable. Typically, a ClassLoader hierarchy is created as more classes are loaded.

CodeSource

Since Java code can be downloaded over a network, the code's origin and author are critical to maintaining a secure environment. Consequently, the object java.security.CodeSource fully describes a piece of code. The CodeSource encapsulates the code's origin, which is specified as an URL, and the set of digital certificates containing public keys corresponding to the set of private keys used to sign the code.

Many access-control decisions are based in part on this property.

Permissions

Permission classes are at the very core of Java security and represent access to various system resources such as files, sockets, and so on. A collection of permissions can be construed as a customizable security policy for an installation.

For example, permission may be given to read and write files in the /tmp directory. Permission classes are additive in that they represent approvals, but not denials. It's possible to explicitly permit the reading of a particular file, but not to explicitly deny the reading of that file.

A number of permission classes are subclasses of the abstract java.security.Permission class, examples of which include FilePermission, AWTPermission, and even customized protections like SendMailPermission. For an exhaustive list of permissions, see Resources for a link to Li Gong's Inside Java 2 Platform Security.

Protection domains

It's possible to associate permissions with classes; however, it's more flexible to group classes into protection domains and associate permissions with those domains. A class's mapping to a domain occurs before the class is usable and is immutable thereafter. For instance, system classes can be effectively grouped under the system domain.

This relationship between the class and the permissions via the protection domain provides for flexible implementation mechanisms.

Policy

The numerous mappings of permissions to classes are collectively referred to as policy. A policy file is used to configure the policy for a particular implementation. It can be composed by a simple text editor or using policytool, a tool bundled with the Software Development Kit (SDK), which we'll discuss in a future article.

SecurityManager

As seen in Figure 4, the class java.lang.SecurityManager is at the focal point of authorization -- the implementation of the sandbox model in JDK 1.0 is a good example.

Prior to Java 2, SecurityManager was abstract; vendors had to create concrete implementations. In Java 2, SecurityManager is concrete, with a public constructor and appropriate checks in place to ensure that it can be invoked in an authorized manner. SecurityManager consists of a number of check methods. For example, checkRead (String file) can determine read access to a file. The checkPermission (Permission perm, Object context) method can check to see if the requested access has the given permission based on the policy. This method is relegated to the access controller in the default implementation. The access controller will raise an exception if the requested permission cannot be granted. Refer to the "Sidebar 2: Security exceptions" for an overview of security exceptions.

AccessController

The java.security.AccessController class is used for three purposes:

  • To decide whether access to a critical system resource should be allowed or denied, based on the security policy currently in effect
  • To mark code as privileged, thus affecting subsequent access determinations
  • To obtain a snapshot of the current calling context, so access-control decisions from a different context can be made with respect to the saved context

While the SecurityManager can be overridden, the static methods in AccessController are always available. If you wish a particular security check to always be invoked, regardless of which security manager is in vogue, the AccessController methods should be called.

The keystore

The keystore is a password-protected database that holds private keys and certificates. The password is selected at the time of creation. Each database entry can be guarded by its own password for extra security. Certificates accepted into the keystore are considered to be trusted. Keystore information can be used and updated by the security tools provided with the SDK.

Aspects of Java 2 Security

Aspects of Java 2 Security can be broadly classified as:

  • Core security: the core classes that deal with security
  • Security extensions: the optional packages that supplement the platform security
  • Security tools: the Java 2 Software Development Kit (SDK) tools pertaining to security
  • Application, applet, and plugin security: security deployment

In this article, I have focused on core security. I'll discuss the other aspects in subsequent articles.

Core security

Java 2's security pieces reside primarily in:

  • java.lang
  • java.security
  • java.security.cert
  • java.security.interfaces
  • java.security.spec

Another Java 2 package, java.security.acl, which exists for historical reasons, has been superseded by classes in the java.security package.

Let's examine each major security-related class in more detail.

java.lang

The java.lang package contains the SecurityManager class discussed above, which allows applications to implement a security policy. Before performing a sensitive operation, the SecurityManager determines the operation's identity and whether it can be performed in its security context. The manager contains many methods that begin with the word check. The invocation of such a check method typically looks like this:

    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkXXX(argument,  . . . );
    }

The special method checkPermission(java.security.Permission) determines whether an access request indicated by a specified permission should be granted or denied. The default implementation calls:

   AccessController.checkPermission(perm);

If a request is allowed, checkPermission returns quietly. If denied, a SecurityException is thrown. Refer to the "Sidebar 2: Security Exceptions" for an overview of security exceptions.

java.security

java.security contains most security classes and interfaces. It contains classes for access control, parameters for the various cryptographic algorithms, code source, guarded objects, key management, message digests, permission, policy, protection domains, providers, secure class loaders, random number generators, and digital signatures.

The following Java code can be used to produce a permission to read files in the /tmp directory.

FilePermission p = new FilePermission("/tmp/*", "read");

Entries in the policy file can also be used to achieve similar results. The following is a sample entry in the policy file that indicates the granularity of providing access.

// Sample policy file
grant signedBy "signer_names", codeBase "URL" {
    permission permission_class_name "target_name", "action", 
    signedBy "signer_names";
    };

Both the signedBy and codeBase name and value pairs are optional. The signedBy entry is an alias corresponding to the public key certificate of the private key used to sign the code. The alias name is mapped to the certificate in the keystore. The entry below would grant read/write access to all /tmp files if the code were signed by "Duke". The URL where the code originated is irrelevant.

grant signedBy "Duke" {
    permission java.io.FilePermission "/tmp/*", "read,write";
};

The entry shown below would provide unrestricted access -- clearly not desirable from a security viewpoint.

grant {
    // Allow everything for now
    permission java.security.AllPermission;
};

Many classes provide a Service Provider Interface (SPI) for providers to plug in their implementations. Examples include MessageDigest, Signature, KeyPairGenerator, and so on.

The MessageDigest class supports the MD5 and SHA algorithms. The getInstance() method is invoked to select the appropriate algorithm. The method update() is called to ready the input buffer, while the digest() method generates a message digest, the size of which (in this case, 128 bits, or 16 bytes) depends on the algorithm (in this case, MD5). The same digest() method would generate 160 bits (20 bytes) for the digest if the SHA algorithm was used. The code below is a complete application used to generate a message digest.

// Simple application that generates a Message Digest
import java.security.*;
import java.io.*;
public class md5 {
    public static void main(String args[]) {
        try {
        if (args.length != 1) {
            System.out.println("Usage: java md5 < some_string >");
            System.exit(1);
        }
        // Create an output file "digest"
        FileOutputStream digestStream = new FileOutputStream("digest");
        // Use the MD5 algorithm. SHA will work as well
        MessageDigest md=MessageDigest.getInstance("MD5");
        byte buf[] = args[0].getBytes();
        // Update the data and digest it
        md.update(buf);
        digestStream.write(md.digest());
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

The Signature class supports digital signatures. The signature algorithm can be, for example, DSA (digital signature algorithm), with SHA-1 (secure hashing algorithm) as the hashing algorithm. (See Part 1 of this series for more on algorithms). The DSA algorithm using the SHA-1 message digest algorithm can be specified as SHA1withDSA. In the case of RSA, there are multiple message digest algorithms possible, so the signing algorithm could be, for example, MD2withRSA, MD5withRSA, or SHA1withRSA. The algorithm name must be specified, as there is no default. Fragments of code to generate a signature are illustrated here:

    // Use SHA-1 as the hashing algorithm with DSA
    Signature sig = Signature.getInstance("SHA1withDSA", "SUN");
    // Initialize with private key
    sig.initSign(privKey);
      // Update the buffer with the message
      sig.update(buffer, 0, len);
      // Generate the signature
      String sigString = sig.sign();

Fragments of code to verify the signature are illustrated here:

    // Use SHA-1 as the hashing algorithm with DSA
    Signature sig = Signature.getInstance("SHA1withDSA", "SUN");
    // Initialize with public key
    sig.initVerify(pubKey);
      // Update the buffer with the message
      sig.update(buffer, 0, len);
      // Verify the signature
      boolean verifies = sig.verify(sigToVerify);

Notice that the signature is generated with the private key and verified with the public key. For more code samples, see Mary Dageforde's tutorial in Resources.

Wrap it up: java.security.cert, java.security.interfaces, and java.security.spec

The java.security.cert package deals with certificates. It provides, for instance, an abstract class to import, generate, and verify X.509 certificates. The java.security.interfaces package is a set of interfaces used to generate DSA and RSA key pairs. Finally, the java.security.spec package may be used to control the parameters for various algorithms, like DSA or RSA, and their corresponding keys.

Conclusion

In this article, we saw the different aspects of Java security, starting with its evolution. We looked at the different components of Java 2 Security and saw how they work together to support the design of a flexible policy.

In my next installment of this series, I'll discuss Java security extensions and the tools available as part of the platform. Finally, I'll touch on issues that affect applet security -- that is, the relationship of browser security to Java applets.

Raghavan Srinivas, a Java technology evangelist at Sun Microsystems, specializes in Java and distributed systems. He is a proponent of Java technology and teaches graduate and undergraduate classes in the evening. He has spoken on a variety of technical topics at conferences around the world, and is a member of the joint IETF/W3C working group on XML Digital Signatures (xmldsig). A software developer for over 15 years, Srinivas worked for Digital Equipment Corporation before joining Sun. He has worked in several key technology areas, including the internals of VMS, Unix, and Windows NT platforms. Srinivas holds a master's degree in computer science from the University of Southwestern Louisiana's Center of Advanced Computer Studies. He likes hiking, running, and traveling, but most of all loves to eat, especially spicy food.

Learn more about this topic

  • JavaWorld security-related articles and resources
  • java.sun.com security-related resources
  • Useful security-related books, documentation, and Websites

Join the discussion
Be the first to comment on this article. Our Commenting Policies