|
The software industry is focused on providing support for
developing and deploying mission-critical applications written in
Java**. The Java environment encompasses a broad spectrum from
enterprise servers to embedded devices. A range of Java-based systems,
including JavaOS**, EmbeddedJava**, and PersonalJava**, among others,
will become available, providing potentially different levels of
underlying services. This situation will result in requirements for
varying levels of security strength.
The initial focus of Java security has been in the support of
downloaded applets (small programs) within World Wide Web browsers. To
a large extent, the security features in Java reflect this heritage. As
Java matures, it will increasingly support additional security features
to address the needs of the target application environments. Included
is the addition of security features generally found in large-scale
server applications.
We have seen examples of how e-business (business conducted
electronically via the Web) increases a customer's reach by orders of
magnitude. As the customer base increases, the absolute magnitude of
losses from malicious behavior can become great enough to warrant
improved security products deployed in information technology systems.
However, should a security exposure become widely publicized, a
customer's reputation can become tarnished.
IBM's customers demand systems
implementations that are nearly flawless and that address the needs of
their enterprise. They look to IBM to
ensure that risks are known and to respond quickly with action when
exposures are uncovered.
Gartner Group's December, 1996, report, Java--Good Start, but
Not Yet Secure,[1] highlights
several areas of concern regarding Java security that are based on
earlier versions of the Java Development Kit (JDK**). Realistic
expectations are important: no system is 100 percent secure. However,
it is crucial to recognize three things about Java security:
- Java enables a function that was never before commercially
deployed on a broad scale: dynamic loading of code from a source
outside the system. This important feature aggravates a significant
"Trojan horse" security problem. However, it also provides
extremely valuable function.
- Java security was not designed to solve the same problems as
the Resource Access Control Facility (RACF*) function in the Operating
System/390 (OS/390*) Security Server addresses on Multiple Virtual Storage
(MVS*), or similar traditional enterprise
security technologies. RACF is designed to
protect the enterprise and its resources against hostile users. The
security for Java is designed to protect the user's workstation and
resources against hostile code.
- RACF and other traditional
enterprise security technologies work well because they assume--and
their environments provide--strong operating system integrity as a
foundation. Java also assumes this integrity; however, the predominant
desktop operating systems do not provide it sufficiently.
Security technologies are needed to prevent or mitigate the
following types of threats:
- Unauthorized resource usage, including theft of software
or CPU usage, corruption of data and
software, disclosure of information, and unaccountable action
- Abuse of privilege, including misrepresentation of identity,
affiliation, value of items exchanged, and entitlement of services;
impersonation; fraud; and extortion
- Malicious code, including protection from viruses, worms,
Trojan horses, and logic bombs
- Wiretapping, including active and passive measures
- Denial of service, including destruction of resources,
saturation of services, and interruptions of communications
Trojan horse and spoofing attacks have been the most common Java
security threats publicized to date. Once identified, it has been
relatively easy to provide fixes as the majority of attacks have
occurred due to implementation errors in browsers or in Java rather
than fundamental design flaws.
Java security foundation and evolution. Since initial
commercial deployments of Java were in Web browsers, much of the focus
of Java security has been in providing features for protecting against
hostile applets; that is, against hostile code downloaded from Web
sites on the Internet. Java security builds upon three fundamental
aspects of the Java run-time environment: the ByteCode Verifier, the
Security Manager, and the ClassLoader.
The ByteCode Verifier ensures that downloaded code is properly
formatted, that bytecodes (Java Virtual Machine instructions) do not
violate the safety restrictions of the language or virtual machine (no
illegal data conversions), that pointer addressing is not performed,
that internal stacks cannot overflow or underflow, and that bytecode
instructions will have the correct typed
parameters.[2]
The Security Manager initiates run-time access controls on attempts to
perform file I/O and network I/O, create new ClassLoaders, manipulate
threads or thread groups, start processes on the underlying platform
(operating system), terminate the Java Virtual Machine
(JVM), load non-Java libraries (native code)
into the JVM, perform certain types
of windowing system operations, and load certain types of classes into
the JVM. For example, the Java applet
sandbox[3] severely constrains
downloaded applets to a
limited set of functions that are considered to be relatively safe.
The ClassLoader determines how and when applets can load code and
ensures that applets do not replace system-level components within the
run-time environment.
In addition, a number of features in the Java programming language and
run-time environment, including automatic memory management and strong
data type safety, facilitate writing safe code.
Through the use of digital signature services provided in
JDK 1.1, trusted applets can be treated in
a manner similar to applications written in Java. That is, these
trusted applets have much greater access to
JVM resources than applets that run in the
restricted Java sandbox. Improved and much more flexible access control
features are the major security addition in
JDK 1.2 and are described in greater detail later in this paper.
Today's computing environments have a number of security weak points
that are addressed by features available in Java prior to JDK 1.2:
- Strong memory protection--Java removes the possibility of either
maliciously or inadvertently reading or corrupting memory locations
outside boundaries of the program. As a result, Java applications or
applets cannot gain unauthorized memory access to read or change
contents.
- Encryption and digital signatures--Java supports the use of powerful
encryption technology to verify that an applet came from an
identifiable source and has not been modified.
- Rules enforcement--Java is completely object-based. By using Java
objects and classes to represent corporate information entities, it is
possible to explicitly state the rules governing the use of such
objects.
Sidebar - "Security functions in JDK releases"
Java run-time environment. Java, as an object-oriented
language, can be used to develop applications in much the same way as C
and C++ are used to improve programmer productivity. In contrast to
many other programming languages, Java provides a standard set of
libraries, including a broad range of communications and security
capabilities, thus simplifying construction and deployment of client/
server and distributed systems applications. It also provides data type
safety and performs bytecode verification when the code is loaded into
the JVM run-time environment. This catches
bugs that arise from programming errors, especially with pointer
arithmetic and array out-of-bounds indexing errors.
Additionally, Java-based support environments exist for the following:
- Applets--downloadable code with restricted access, usually coupled
with a browser
- Aglets--code pushed to the end device rather than the pull model of
applets
- Servlets--Java code on the server
- Orblets--code utilizing object request broker communication mechanisms
Any of these environments may utilize a component model for
reusability, such as JavaBeans**[4] and
Enterprise JavaBeans**.[5]
Java is evolving to support a multitude of system configurations:
- Embedded systems destined to be found in many consumer devices in
the home and elsewhere
- Java-based smartcards
- Personal management devices such as pagers and
PDAs (personal digital assistants)
- Network computers--thin and flat clients
- Mobile systems
- Highly scalable enterprise servers
To support this range of configurations, a family of
products--including JavaOS, PersonalJava, Enterprise Java, and
JVMs hosted in application builder
environments--is evolving to meet the unique needs found in the
respective environments.
Security requirements vary depending on the unique characteristics of
these substantially different environments. Initial work indicates that
a common security model is likely and that the environmental
differences can be supported by providing extensions, rather than
deploying significantly different security models. The use of a common
security model will simplify and reduce the cost of application and
library development and deployment.
Sidebar - "Java security with respect to components"
JDK 1.2 permissions model
The discussion below is based on beta-level code; some operational changes
may result from industry feedback prior to general availability.
JDK 1.2 introduces a number of new
security features that make it easier to enforce access control of
protected resources.[6] In earlier
versions of Java, JVM resource access was enforced by the
"sandbox" security model. Extensions were usually limited to
features implemented by the platform provider (e.g., browser, Web
server). The new JDK 1.2 permission model
is much more flexible and even permits application-defined resources to
be added to the access control system. Java programs now have the
ability to define access restrictions on sensitive resources without
requiring the writing of a new Security Manager or modifying the
underlying platform. This means that applets downloaded into a browser,
or servlets loaded into a Java server, can add resource access controls
to a JVM without having to modify the
underlying browser or server implementation.
One of the notable features of the new security model is that most of
the access control implementation is contained in the Java security
subsystem. Typically, Java programs (e.g., applets, servlets) and
components or libraries (e.g., packages, beans) do not need to contain
any access control code. When a program wants to add protected
resources to the JVM, a method call can be
added that will check whether the restricted operation is permissible.
One general technique employed in JDK 1.2
is to create a guarded object, whereby access to an object
or operations on an object are restricted by an access control call.
Examples of how to use the access control features are shown later in
this paper.
The JDK 1.2 access control subsystem
introduces new concepts. The first is
CodeSource, which is the combination of a
set of signers (digital certificates) and a codebase
URL (uniform resource locator). The
CodeSource is the basis for many
permission and access control decisions. The second concept is the
security policy. The policy contains a number of grant
entries that describe the permissions granted to a particular
CodeSource (see the
"Policy Database"
sidebar, later). A grant entry may contain one or more
Permissions, which is the right
to access or use a protected resource or guarded object. Lastly, a
ProtectionDomain is an aggregation of a
CodeSource and the
Permissions granted for the
CodeSource as specified in the policy
database. Each class file loaded into the
JVM via a ClassLoader is assigned to a
ProtectionDomain, as determined
by the CodeSource of the class.
Loading Java programs. The three legs of
JVM security are the ByteCode Verifier,
the Security Manager, and the ClassLoader. Prior to
JDK 1.2, each application had to write its own subclasses of
SecurityManager and ClassLoader.
JDK 1.2 simplified the development process
by creating a subclass of ClassLoader
called SecureClassLoader.
SecurityManager no longer is abstract and
can be instantiated or subclassed. Most of its methods now make calls
to methods in class AccessController, which provides
the access control function in the JDK 1.2. Since most of the
SecurityManager methods call
AccessController, this greatly simplifies
the writing of new SecurityManager subclasses.
To automatically invoke the new security subsystem, a Java application
is started from the command line of a native operating system. The Java
run time creates an instance of
SecureClassLoader, which in turn is used
to locate and load the class file of the application. A subclass of
SecurityManager is created and installed
in the Java run time. The main()
method of the application is then called with the command line
arguments.
The purpose of the change in the Java run time for starting Java
applications is twofold. First, a simple
SecurityManager is installed in the
system that uses the new Java security access control subsystem.
Second, a SecureClassLoader is used to
safely and correctly load classes into the Java run time.
SecureClassLoader has several important
purposes. The first is to make sure that searching for classes is done
in the correct order. When the JVM needs a
class, SecureClassLoader first looks for
files referenced by the classpath of the
JVM to see whether it is available. Files
in the classpath are intended to be the completely trusted classes that
are part of the Java run time. For example, all of the code shipped
with the JVM is included in the classpath,
and is therefore considered trusted code. If not found in the
classpath, an application-defined location can be searched (e.g., a Web
server via a URL request). Finally, code
may be part of Java Standard Extensions, which is a set of classes that
are available in the host file system but are not part of the
JVM classpath. Classes in the Standard
Extensions are typically located on the disk drive of the host system
(e.g., the workstation or personal computer), but the classes are not
part of the fully trusted run-time classes of the
JVM.
The second important purpose of
SecureClassLoader is to create and set
the ProtectionDomain information for
classes loaded into the JVM. When the
SecureClassLoader loads a class into the
JVM, the codebase URL and the digital certificate used to
sign the class file (if present) are used to create a
CodeSource. The
CodeSource is used to locate (or
instantiate) the ProtectionDomain for the
class. The ProtectionDomain contains the
Permissions that have been granted to the
class. Once the class file has been loaded into the
JVM, SecureClassLoader assigns the appropriate
ProtectionDomain to the class. This
ProtectionDomain information, and, in
particular, the Permissions in the
ProtectionDomain, is used in determining
access control during run time.
Once a Java program starts to run, the
SecureClassLoader assists the
JVM in loading other classes required to
run the program. These classes are also assigned the appropriate
ProtectionDomains based on their
CodeSource.
Run-time access controls. At various points during the
execution of a Java program, access to protected resources is
requested. Such access includes, but is not limited to, network
I/O attempts, local file I/O, or attempts to create a new
ClassLoader or to access a program-defined resource. To verify whether
the running program is allowed to perform the operation, the
library routine makes a call to the
SecurityManager'scheckPermission(permissionToCheck)
method, which subsequently calls
AccessController.checkPermission(permissionToCheck).
These method calls are responsible
for determining whether the current thread has sufficient permissions.
checkPermission() takes a
Permission object as an argument. The
AccessController method
checkPermission() walks back
through the stack frames of the current thread, obtaining the
ProtectionDomain for each of the classes
on the thread's stack (see the section on "Thread stack frames").
As each ProtectionDomain in the thread
stack is located, the permissionToCheck
is compared to the Permissions contained
in ProtectionDomain. For each stack
frame, if permissionToCheck matches one
of the Permissions in the
ProtectionDomain, testing of the
Permissions continues with the
ProtectionDomain of the next stack frame
(class) on the stack. This testing repeats until the end of the stack
is reached. That is, all of the classes in the thread have permission
to perform the operation. Thus, the access control check succeeds,
typically meaning that the requested operation is able to proceed. If
permissionToCheck is not granted to all
classes on the stack (there is no appropriate
Permission in all of the
ProtectionDomains of the classes),
then a SecurityException is thrown, and
access to the resource is denied.
A flaw in the above scenario is when a class has a set of
Permissions and does not care who its
callers may be, as for example, a JavaBean installed on a desktop
computer needing to read files from the local disk drive. The
ProtectionDomain of the bean's class has
a Permission to read these local files.
However, the program loaded from a Web server that calls the bean has a
ProtectionDomain that does not
have local file read permission. Normally, if the bean were called by
the program loaded from the Web server, the bean would be denied access
to the files on the local disk drive because the program from the Web
server does not have a local file read
Permission. However, if the bean calls
AccessController.beginPrivileged(), an annotation is
made on the stack frame of the thread, indicating that when
AccessController.checkPermission(permissionToCheck)
searches for ProtectionDomains, the
search stops at this stack frame. The bean may make any number of
method calls, but when
AccessController.checkPermission(anotherPermissionToCheck)
is called, the search back through the stack frames to find
ProtectionDomains stops at this stack
frame. Based on the above scenario, the
ProtectionDomains for the bean will be
checked, but the ProtectionDomains for
the program from the Web server are not checked since the search
stopped at the stack frame for the bean. Therefore, the file read
operation will succeed. To turn off this privileged mode of operation,
a call to AccessController.endPrivileged()
removes the stack annotation. If the application were to forget to call
AccessController.endPrivileged(),
the JVM gracefully recovers because the
beginPrivileged() and
endPrivileged() call are
associated with the stack frames. That is, once the method that called
beginPrivileged() exits, the
privileged mode is automatically turned off.
A subtle aspect of the above
beginPrivileged()/endPrivileged()
operations is that programs creating new threads would lose
ProtectionDomain information when a new
thread is created. That is, each new thread creates a new run-time
stack. The classes on the stack of the parent thread are not present in
the new thread. Important
ProtectionDomain information is no longer
available when a checkPermission()
operation is performed. This would give new threads more permissions
than the threads that created them. To get around this apparent loss of
security information, the
ProtectionDomains of the parent thread
are attached to (inherited by) a child thread when it is created. So,
unless a beginPrivileged()
operation is performed in the child thread, the
ProtectionDomains of the parent thread
are also checked during a checkPermission() operation.
Thread stack frames
Each thread in the JVM contains a number of stack
"frames." Simply stated, these frames contain the method instance
variables for each method called in the current thread. If a program
debugger were used, the debugger would be able to show the instance
variables for each of the methods on the stack. For further
clarification, a couple of examples are offered.
Example 1: Simple check of the current thread.
Figure 1 shows a snapshot of a program,
called MyProgram, with a codebase URL of
http://www.NominallyWidgets.com.
After the security subsystem has
been initialized, the program tries to get a system property by calling
System.getProperty("java.home"). The
getProperty method calls the Security Manager method,
checkProperty(), to see whether
the current thread is allowed to read a system property. In turn, the
Security Manager calls the method
AccessController.checkPermission with an
argument of PropertyPermission("java.home",
"read") to see whether all of the classes on the
stack have the appropriate permissions.
Figure 1
Note that as of the writing of this paper, classes loaded via the
JVM classpath are not assigned to a
ProtectionDomain.
These classes logically are assigned to the system domain, which has
unrestricted access to all Java and application-defined resources. That
is, when the AccessController does its
checking, it assumes system domain classes have all permissions.
The access control in this example works as follows:
- Class
java.security.AccessController
is in the system domain. By default, the system domain has implicit
permission to read the properties; checking is allowed to proceed to
the next stack frame.
- The class
java.lang.SecurityManager is in
the system domain. By default, the system domain has implicit
permission to read the properties; checking is allowed to proceed to
the next stack frame.
- Class
java.lang.System is in the system
domain. By default, the system domain has implicit permission to read
the properties; checking is allowed to proceed to the next
stack frame.
- MyProgram has a
ProtectionDomain with a
codebase of
http://www.NominallyWidgets.com.
The permissions for this
ProtectionDomain are checked. If the
permission is not granted, a security exception would be thrown, and
the getProperty() method call
would fail. If the permission is granted, checking is allowed to
proceed to the next stack frame. In this example, the permission is
granted, so checking is allowed to proceed to the next stack frame.
- MyProgram has a
ProtectionDomain with a codebase of
http://www.NominallyWidgets.com.
The permissions for this ProtectionDomain are checked. The
permission is granted; checking is allowed to proceed to the next stack
frame.
- The class
java.lang.Thread is in the
system domain. Since system domain has the implicit permission to read
the properties, checking is allowed to proceed to the next stack
frame.
In this example, the operation is permitted since the checks
succeeded for all stack frames.
If this thread had been created by another thread with
ProtectionDomains in any of its stack
frames, the inherited ProtectionDomains
from the parent thread would also be checked.
Obviously, there is room for optimization because many of the
ProtectionDomains on the thread's stack
are not unique. In practice the
Permissions in each unique
ProtectionDomain are checked only once
per call to checkPermission().
Example 2: beginPrivileged() was called. In this
example, a thread was created, and a program calls a bean (MyBean)
containing a protected resource. MyBean calls
AccessController.beginPrivileged()
and then calls a method in
MyBeanProtected that will check to see
whether the thread has the appropriate permissions.
The thread's stack is presented in Figure 2.
Figure 2
The access control works as follows:
java.lang.SecurityManager is part of
the system domain, so it has implicit permission to access the
resource. Proceed to the next stack frame.
MyBeanProtected is part of the
ProtectionDomain that has access to the
resource. Proceed to the next stack frame.
- MyBean is part of the
ProtectionDomain
that has access to the resource. Since
AccessController.beginPrivileged()
was called from this stack frame, stop here; do not check any more
stack frames.
The following two-stage algorithm describes how
AccessController computes permissions.
The first step is to obtain a list of
ProtectionDomains used by the second step
as shown in Figure 3.
Figure 3
The second step is to check with each of the
ProtectionDomains to see whether it
contains the permission being checked
(Figure 4).
Figure 4
If an exception is not thrown, the requested operation is permitted.
The flowcharts in Figure 5 and
Figure 6 graphically indicate how
the new functions just described relate.
Figure 5
Figure 6
Tools
There are three important security tools and two data repositories
in JDK 1.2. These tools are primarily oriented for users of the JDK
rather than for end users of applications that incorporate Java.
They are described below.
The Java archive (JAR) utility tool (here
represented as jar) was designed mainly to facilitate the
packaging of Java object files and resources into a single file, or
archive. When Java components (class, image, audio, etc.)
are placed in an archive, they can be downloaded via a single
HTTP (HyperText Transfer Protocol)
transaction with a server rather than requiring a separate
HTTP connection for each downloaded
component. Network download performance is generally improved,
especially since the jar tool can compress the contents of the archive.
Cryptographic key management, keytool, is a cryptographic
key and certificate management utility. It, along with jarsigner (see
below), replaces the JDK 1.1
javakey tool. The keytool utility allows developers to
administer their own public or private cryptographic key pairs and
associated certificates for use in client authentication, or for data
integrity and authentication services, requiring digital signatures.
This utility also allows the caching of the public key of a
communicating peer (e.g., a Web server).
Keytool manages a keystore (repository) of private keys and
the associated X.509 certificate chains authenticating the
corresponding public keys. The keystore may be protected with a
passphrase (as in the default implementation from JavaSoft) or by a
stronger protection mechanism (e.g., cryptography). There
are two basic entries in the keystore:
- Key or certificate entries--consist of a private key and a
certificate chain
- Trusted certificate entries--multiple single certificates with public
key entries
Keytool can create a keystore, clone or delete entries in a
keystore, import certificates (trusted and nontrusted), export
certificates, display the contents of the keystore, and generate
self-signed certificates (including public or private key pairs).
Both Java 1.1 and 1.2 implementations of keytool only support the
DSA (Digital Signature Algorithm) key and
the DSA/SHA-1 (Secure Hash Algorithm) signature algorithms.
The key size is limited to a maximum of 1024 bits.
For backwards compatibility, it is expected that
JDK 1.2 will be able to parse or process the JDK 1.1 keystore format.
The utility for signing JAR files
digitally, jarsigner, has two major functions: sign jar
files, and verify the signature(s) and integrity of signed jar files.
The certificate(s) contained in a jar file are used by jarsigner to
verify the digital signature(s) and to verify whether or not the public
key of the certificate(s) is contained within the specified keystore.
Also, jarsigner verifies that the jar file has not been tampered with
in any way. Currently jarsigner can only sign jar files that were
created with the jar utility.
Jarsigner can sign jar files using either the DSA key algorithm, with the
SHA-1 digest algorithm (the default cryptographic engine provider supplies the
DSA/SHA-1 algorithms) or the RSA** (derived from the original founders: Rivest,
Shamir, and Adleman) key algorithm with the MD5 (modification detection) digest
algorithm. That is, if the signer's public and private keys are
DSA, jarsigner uses the DSA/SHA-1 algorithms. If
RSA keys are provided, the RSA/MD5 algorithms are used.
A jar file may be signed more than once by simply running jarsigner
more than once, specifying a different signer each time.
Access control policy database management is handled by
policytool, a graphical user interface that assists a user,
such as a system administrator, in specifying, generating, editing,
exporting, or importing a security access control policy for the
JVM. The tool creates a policyfile as described below.
As described above, the keystore, cryptographic key storage,
is a repository of private keys and the associated X.509 certificate
chains authenticating the corresponding public keys. The keystore is a
concrete implementation of the keystore class provided in the
java.security package. All three utility programs described above use
the keystore.
The policy for a Java run time, specifying which permissions are
available for code from various code sources, is represented by a
Policy (access control policy database) object. More
specifically, it is represented by a Policy subclass providing an
implementation of the abstract methods in the Policy class (which is
defined in the java.security package). A default implementation of
Policy, called PolicyFile, reads the
policy information from flat ASCII files.
The policy framework allows policy information to be stored on the
local system or anywhere in the network.
The policy configuration file(s) for Java 1.2 installation specify what
permissions (which types of system resource accesses) are allowed by
code from specified code sources. The information stored in the policy
database is described in the "Policy Database" sidebar.
Java and browser-based security models
Browsers and other Internet technologies were in the marketplace
prior to the broad introduction of Java; consequently, it is not
surprising that mismatches exist in the security models provided by the
rapidly evolving Java related technologies. Major progress toward
synchronization can occur if agreement can be reached on selected parts
of the models in an orderly fashion. The mismatches result primarily
from the early limitations of Java security and the desire to fill
these voids to meet customer requirements. One such area where there is
a concerted effort in model alignment is in the access control model.
This section provides a high-level view of the capability classes of a
representative browser with comparable features found in the Java 1.2
security architecture. Both use a stack-based approach to
authorization. However, they employ significantly different access
control models and authorization mechanisms. The browser's proposal is
more ambitious, but the added functionality may be more difficult to
manage.
It should be recognized that mismatches also exist among browser
security models as they expand support to cover the hosting of
HyperText Markup Language (HTML) pages,
scripts, and applets. In some environments, signed Java applets will
have reduced access to some Java elements because the containing page
or script calling the applet is not signed. Only the Java access
control mechanisms of the browser will be highlighted in the following
discussion.
Alternative access control model. A commercially available Web
browser's Java Virtual Machine implementation that uses an alternative
access control model based on targets and explicit activation or
deactivation of permissions was
examined.[7,
8] A target is a
mapping of a principal to an operation on an object (i.e., roughly a
traditional permission representation). Whether a permission can be
enabled or not depends on a three-valued logic for "enabling"
policy. Essentially, if at least one principal (user, systems
administrator, target class definer) permits the target and no
principal forbids the target, the target can be enabled. Java
developers can enable such targets at run time. Also, developers can
disable (forbid) or revert (undo enabling of) targets at run time. This
model enables the policy of multiple principals to be combined and less
than maximal rights to be granted to an applet. It is up to the applet
code to manage this subset of enabled permissions.
At run time, targets are activated by
enablePrivilege(), prohibited by
disablePrivilege(), and
deactivated by revertPrivilege().
Reverting only affects the calling stack frame, so targets enabled in
previous stack frames are still active and supersede the reversion.
Therefore, it is not possible for a descendant method to remove a
privilege, possibly enabling security exposures.
The browser authorization mechanism checks the stack for an enabled
privilege for the request. If one is found, the operation is permitted
regardless of the trust of the classes in the thread's stack (the
method's callers). Therefore, explicit prohibition or proper
permission reversion of rights is necessary to prevent an unauthorized
principal from using another's "enabled" privilege. Note that
there may be other methods that enabled the permission higher in the
thread stack.
If a method in the Java core classes (the basic Java run time) requires
the addition of access controls to close a security hole, the browser
model described in this section will cause trouble for existing code.
It is straightforward to add a permission to allow the code to enable
the permission. However, the code does not already contain
code to enable the permission since the permission was not required
when the code was originally written. The same would be true for
library and bean writers who discover in subsequent versions of their
software that they need to add access controls. The implication is that
application writers would have to update their code to support (i.e.,
enable) any new permissions added to the base Java classes or libraries
and beans they employ. This results in severe version management
problems. This problem does not exist with the
JDK 1.2 access control model. In
JDK 1.2, only an update to the policy database is required.
In summary, the browser model provides more functionality than the Java
model, but this functionality places more responsibility on programmers
to track granted and retracted rights and to be aware of browser,
JVM, or library/bean version differences.
Stack-based authorization
The current access control mechanisms in Java are based on
"stack introspection," or logically walking the stack frames of the
thread to see whether the calling methods or classes have sufficient
permissions to perform a requested operation. Given the coarse
granularity of the current permission structure, the performance of
stack-based authorization appears to be acceptable. However, since all
security issues cannot be reduced to stack introspection, it is
possible for one object to pass rights to another object and obtain
information that it could not otherwise directly obtain. For example,
it may be possible to induce another object to pass a right (e.g., a
file descriptor) to an unauthorized object. The unauthorized object was
not on the stack when the file descriptor was created, yet it still
received the object reference ("off-the-stack" spoofing). This
style of security attack is inherent to stack-based authorization
techniques because the technique does not track all unsafe interactions
between objects.
In environments such as those with stringent communication requirements
(e.g., requiring hierarchical, lattice-like, communication protocols
regarding information flow) the Java security model may not be
adequate. However, for most of the envisioned uses of Java, stack
introspection-based access control features are adequate for
implementing mission-critical applications.
Security requirements--a high-level view
The following requirements address needs identified beyond the
level of function currently planned to be delivered in
JDK 1.2; discussions are underway with
JavaSoft on many of them, and some may be addressed by the time of
JDK 1.2 or in follow-on JDK releases. These are representative
categories and examples of requirements within each category, not
necessarily a complete compilation of known requirements within each
category.
- Java Virtual Machine high-integrity computing
environment: The requirement is to support concurrent applet or
servlet execution with multiple sets of security credentials. Because
authentication and credentials requirements vary between systems, and
sometimes between subsystems, it is necessary for the applets or
servlets within a JVM to support handling
multiple sets of security credentials. Simplified
APIs (application programming interfaces)
will make it easier for application writers to exploit these
security features.
Satisfaction of this requirement should enable secure
interactions between clients and servers and between server subsystems
as is needed for e-commerce applications.
- Policy-driven Java security model and security
services: Customers should be able to define and deploy a security
policy. The underlying systems (e.g., Java) should have sufficient
mechanisms to implement and enforce that policy. Mechanisms for
enforcing policy need to include support for: access control,
cryptographic and quality of protection, trust, secure delivery of
policy statements to the JVM, policy
administration, and JVM support of a policy engine.
Satisfaction of this requirement may reduce the total cost
of ownership through simpler configuration and policy administration by
making the JVM a single point of policy
enforcement for all Java applications.
- Simple security programming models: Require simple
high-level APIs for quality of protection
(privacy, integrity, and nonrepudiation) to allow security-unaware
applications to obtain default security protection for such functions
as secure communications, secure documents or mail, secure streams, and
secure remote method invocation (RMI) and Internet Inter-Orb Protocol
(IIOP).[9]
This requirement may simplify the programming model,
making security permissions readily available to any application and
reducing potential mistakes made by security-naive programmers.
- Standards for secure deployment of applications: A
manifest format and single signature standards (such as
W3C) for applet and application delivery
are needed.
This requirement should establish a single and low-cost
way of ensuring the delivery of applications both to the server and
client systems.
- Standardized programming models: Create standards for
establishing trust and integrity of applications consisting of multiple
applets embedded in HTML or XML (Extensible Markup Language) documents.
This requirement may provide a single model for the
allocation of access control or other policy to an application that may
consist of a number of componentized elements.
- Native security services support: Utilize the security
features of the underlying platform.
This requirement should maximize platform security
functionality, improve performance, and allow for consistency.
- Standards for development and deployment of (cryptographic)
service providers: Cryptographic service providers should be
signed and provide enumerated descriptions of the services within the
provider.
This requirement should ensure that international
deployment requirements are met and needed information is accessible to
applications requiring these services. Cryptographic services can be
deployed internationally with control of the strength of cryptographic
operations (e.g., key length, signature length, algorithm strengths,
etc.).
- Maintainability, scalability, and
interoperability: Provide centralized administration and the
ability to react to changes, the ability to interoperate and utilize
non-Java security capabilities, and the ability to support full
security functionality in a distributed manner as needed between
clients and servers.
This requirement should allow for the controlled
deployment of Java and improves performance and migration.
- Removal of security as an impediment to
performance: Allow hardware-supported or native-supported
implementations of algorithms to be used during validation of class
files, and allow policy to define where a combination of trusted signer
and usage of a trusted compiler will permit the override of dynamic
bytecode verification at class load time.
Many security functions are highly performance-intensive
(e.g., hashing, key generation); improvements are needed to approach
performance found in non-Java environments.
It should be noted that Java does not run in
isolation; it runs in the context of the operating system platform on
top of which it has been implemented. In addition, Java is frequently
embedded inside another application, such as a Web browser or Web
server. Each of these operating systems and subsystems has an impact on
the JVM and Java run-time vulnerability to
security attacks.[10-13] When
deploying Java programs, care
should be taken in configuring the JVM and
application files to minimize vulnerability to security attacks; this
discussion is outside the scope of this paper.
Future directions
With continued strong support from the software industry, many
enhancements required to bring the Java environment in line with the
most stringent needs generally provided in the non-Java environment
today are possible. These features include strong encryption,
sophisticated access control, and the ability to provide centralized
security policy management. Some of these capabilities are likely to
become available beginning in mid-1998 and throughout 1999. Much work
is also underway to provide Java extensions for accessing existing
industrial-strength security mechanisms, thus improving system
integrity, performance, and functionality. In the future, we will also
see high-level e-commerce and other types of applications migrate from
individually provided security capabilities to utilize the capabilities
in the latest release of the JDK. By late
1998 or early 1999, significant initial security functionality should
be expected in all Java environments.
IBM, Lotus, and Tivoli are working
vigorously with the Java software industry to bring the
business-critical enterprise security requirements forward and ensure
that they are met. IBM's Java initiative
is acutely aware of the need to focus on securing the Java environment.
Acknowledgments
The authors would like to thank Li Gong, the Java security
architect at Sun Microsystems, for in-depth reviews of
JDK 1.2 security, as well as reviewing
this paper. We would also like to thank the following people from
IBM who are actively involved in improving
the Java security environment and to whom much insight and original
thinking influenced ideas in this paper: Bob Blakley III, Trent Jaeger,
Paul Karger, Peter Thull, and anonymous reviewers.
Thanks also to JavaSoft's security group for development
insights for JDK 1.2 and for the ongoing
relationship between IBM/Lotus and
JavaSoft in evolving the Java functionality to meet enterprise class
security needs.
*Trademark or registered trademark of International
Business Machines Corporation.
**Trademark or registered trademark of Sun Microsystems, Inc.
or RSA Data Security, Inc.
Cited references
Accepted for publication March 20, 1998.
|