Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (5.59 MB, 702 trang )
10.1.2 Guard Interface and GuardedObject Class
The Guard interface is provided to create an object used to guard a protected
resource. The supplier of the resource can create an object representing the
resource, encapsulate it into a GuardedObject, and keep the resource inside
this GuardedObject. In creating the GuardedObject, the supplier also
specifies the Guard object. The consumer of the resource can access the
resource object only if the security checks inside the Guard object are
satisfied.
The relationship between the GuardedObject, the Object and the Guard can
be seen in the constructor of the GuardedObject class:
public GuardedObject(Object object, Guard guard)
The only method in the Guard interface is called checkGuard(). It takes an
Object as its argument and it performs security checks to determine whether
or not to allow access to that object.
The Permission class in java.security implements the Guard interface. For
example, suppose a system thread is asked to open a file D:\itso\redbook.lwp
for read access, but the system thread does not know who the requester is or
under what circumstances the request is being made. Therefore, the system
thread can use the GuardedObject class to delay the access control
checking, as follows:
FileInputStream fis = new FileInputStream("D:\\itso\\redbook.lwp");
FilePermission fperm = new FilePermission("D:\\itso\\redbook.lwp", "read");
GuardedObject guardFile = new GuardedObject(fis, fperm);
Now the system thread can pass the guardFile object to the consumer thread.
For that thread to obtain the file input stream, it must call:
FileInputStream finps = (FileInputStream) guardFile.getObject();
The getObject() method in turn invokes the checkGuard() method on the
Guard object fperm, and because fperm is a Permission, its checkGuard()
method is:
public void checkGuard(Object object) throws SecurityException
{
SecurityManager sm = System.getSecurityManager();
if (sm != null)
sm.checkPermission(this);
}
298
Java 2 Network Security
This ensures that a proper access control check takes place within the
consumer context.
10.1.3 Providers
The java.security package also supplies a Provider class. The term
cryptographic service provider (provider for short) is used to refer to a
package or set of packages that supply a concrete implementation of a
subset of the cryptography aspects of the Java security API. The Provider
class is the interface to such a package or set of packages.
As we will see in 13.3.1, “The Provider Concept in the JCA” on page 485, for
each engine1 class in the API, a particular implementation is requested and
instantiated by calling a getInstance() method on the engine class, specifying
the name of the desired algorithm and, optionally, the name of the provider
whose implementation is desired. If no provider is specified, getInstance()
searches the registered providers for an implementation of the requested
cryptographic service associated with the named algorithm. In any Java
Virtual Machine (JVM), providers are installed in a given preference order
specified in the java.security file. That order is the order in which they are
searched when no specific provider is requested. If the implementation is
found in the first provider, it is used. If it is not found, it is searched for in the
second provider and so on. If it is not found in any provider, an exception is
raised. The getInstance() methods that include a Provider argument enable
developers to specify which provider they want an algorithm from. A program
can also obtain a list of all the installed Providers using the getProviders()
method in the Security class and choose one from the list.
Each provider class instance has a (currently case-sensitive) name, a version
number and a string description of the provider and its services. These three
pieces of information can be obtained by calling the methods getName(),
getVersion() and getInfo(), respectively.
10.1.3.1 Installing and Configuring Providers
Providers can be installed by first copying the package in the system and then
configuring the provider itself:
1. To install the provider classes, you can simply place the JAR file(s)
containing the classes anywhere on the user class path (see 3.4.3,
“Application Class Path” on page 88) or even on the boot class path (see
3.4.1, “Boot Class Path” on page 84). However, the best solution is to
supply the provider library as an installed or bundled extension, by placing
1
Engine is a term used to depict an abstract representation of a cryptographic service without a concrete
implementation.
Security APIs in Java 2
299
the JAR file(s) in the extensions directory, as explained in 3.4.2,
“Extensions Framework” on page 86.
For example, to install JCE 1.2 on your Java 2 SDK, Standard Edition,
V1.2 system, you can copy the JAR file jce1_2-do.jar in the extensions
directory. This directory is indicated as the value of the java.ext.dirs
system variable (see Appendix A, “Getting Internal System Properties” on
page 641).
Following these directions, you will be able to run all the programs that use
this particular provider. If you also need to develop and compile programs
using this provider, then the JAR file containing the provider classes must
be also copied in the extensions directory under the JRE development
directory (see 8.1, “A Note on java.home and the JRE Installation
Directory” on page 225).
2. Next, you need to configure the provider. For this you simply need to add it
to your list of approved providers.
• This is done statically by adding the provider to the security provider list
in the java.security file (see 8.3, “The Security Properties File,
java.security” on page 234).
For example, to configure JCE 1.2 on your Java 2 SDK, Standard
Edition, V1.2 system, the security provider called SunJCE must be
provided together with the SUN provider in the java.security file, as
shown:
security.provider.1=sun.security.provider.Sun
security.provider.2=com.sun.crypto.provider.SunJCE
As we have mentioned, the order number with which the provider is
added to the list is very important, in that if an implementation is
supplied in multiple providers, the implementation of the provider with
the higher preference (corresponding to the lower order number) is
chosen by the JVM.
In the same way, a provider is removed by simply deleting the entry
corresponding to it in the java.security file.
• Providers may also be registered dynamically. To do so, call either the
addProvider() or insertProviderAt() static method in the
java.security.Security class.
For example, to add the JCE 1.2 provider SunJCE dynamically, you can
use the following two lines of code:
300
Java 2 Network Security
Provider sunJce = new com.sun.crypto.provider.SunJCE();
int pos = Security.addProvider(sunJce);
The addProvider() method adds a new provider at the end of the list of
the installed providers.
On the other hand, the insertProviderAt() method adds a new provider
at a specified position in the array of providers. If the given provider is
installed in the requested position, the provider that used to be at that
position, and all the providers with a position greater than that, are
shifted up one position, toward the end of the list of the installed
providers.
Both the methods return the preference position in which the provider
was added, or -1 if the provider was not added because it was already
installed.
If the preference position of a provider has to be changed, the provider
must be first removed, and then inserted in back at the new preference
position.
A provider can be removed by calling the removeProvider() method of
the java.security.Security class.
Notice that the dynamic provider registration is not persistent and can
only be done by trusted programs or, in other words, programs that
have been granted the necessary permissions:
• To add a provider, or insert it in a specified position in the list, the
permission required is:
permission java.security.SecurityPermission "insertProvider.name"
• To remove a provider, the permission required is:
permission java.security.SecurityPermission "removeProvider.name"
Note that the SunJCE provider relies on some of the algorithm
implementations supplied by the SUN provider, which is the default provider
of the Java 2 SDK platform. This means that when you install the SunJCE
provider, you need to make sure that the SUN provider is also installed. We
will see more details on this in Chapter 13, “Cryptography in Java 2” on page
475.
10.1.4 The Security Class
As we mentioned in 10.1.3, “Providers” on page 299, the package
java.security also provides a Security class to manage installed providers and
Security APIs in Java 2
301
security-related properties. It only contains static methods and is never
instantiated. Its methods fall into two categories:
1. Methods used to get the installed providers, and also to add, delete, and
insert providers
• The method getProviders() can be used to get the list of all installed
providers. They are returned in a Provider array in the order of their
preference.
• The method getProvider() returns the Provider object specified in the
argument.
• The method addProvider() is used to add a provider to the end of the
list of installed providers, as shown in Step 2 on page 300. These
methods returns the preference position in which the provider was
added, or -1 if the provider was not added because it was already
installed.
• The method insertProviderAt() is used to add a new provider at a
specified position. This method returns the actual preference position
in which the Provider was added, or -1 if the provider was not added
because it was already installed, as shown in Step 2 on page 300. You
cannot install a provider that is already installed. If you need to change
the preference order, you must first remove the provider and then insert
it in the specified position.
• To remove a provider, use the removeProvider() method (see Step 2 on
page 300).
2. Methods used to get and set system-wide properties
The Security class also has methods to manage security properties. A
security property is accessible with the getProperty() method and can be
set with the setProperty() method. However, only trusted programs, or
programs with specific permissions, can use these methods.
The following program shows how to get the information about a provider:
import java.security.*;
class ProviderInformation
{
public static void main(String[] args)
{
String providername;
Figure 128. (Part 1 of 2). ProviderInformation.java
302
Java 2 Network Security
try
{
providername = args[0];
Provider myprov = Security.getProvider(providername);
if (myprov != null)
{
String info = myprov.getInfo();
System.out.println("\n\n" + info + "\n\n");
}
else
System.out.println("No provider with the speicified name is installed");
}
catch (Exception e)
{
System.out.println("There was an exception. The exception was " +
e.toString());
}
}
}
Figure 129. (Part 2 of 2). ProviderInformation.java
Compile this program with the command:
javac ProviderInformation.java
Then you can run it and pass it a provider name on the command line. By
default, the only security provider that comes with the installation of Java 2
SDK, Standard Edition, V1.2 is SUN, the provider supplied by Sun
Microsystems (see 8.3, “The Security Properties File, java.security” on page
234). By launching the command:
java ProviderInformation SUN
you would see the following output:
SUN (DSA key/parameter generation; DSA signing; SHA-1, MD5 digests;
SecureRandom; X.509 certificates; JKS keystore)
The information above shows the features of the SUN provider, listed in 8.3,
“The Security Properties File, java.security” on page 234.
Assuming that you have installed JCE 1.2 on your Java 2 SDK, Standard
Edition, V1.2 system, as indicated in Step 1 on page 299 and Step 2 on page
Security APIs in Java 2
303
300, you can invoke the above program and pass the SunJCE provider name
on the command line, as shown:
java ProviderInformation SunJCE
The output in this case is:
SunJCE Provider (implements DES, Triple DES, Blowfish, PBE, Diffie-Hellman,
HMAC-MD5, HMAC-SHA1)
The information above shows the features of the SunJCE provider, listed in
Point 2 on page 492.
10.1.5 Access Control APIs
The java.security package provides the AccessController class used to make
access control decisions based on the security policy in effect. It is also used
to mark code in the execution stack as privileged (see 3.2.3.1, “Lexical
Scoping of Privilege Modifications” on page 76), thus affecting subsequent
access determinations. Finally, this class is used to obtain a snapshot of the
current calling context, so that access control decisions from a different
context can be made with respect to the saved context.
A thread's security context is based upon the classes on its execution stack.
Each class is associated with a single protection domain which, in turn,
specifies the permissions granted to that class (see 3.3, “Java 2 Protection
Domain and Permissions Model” on page 80). So a security context can be
thought of as a stack of protection domains corresponding to the classes on
the stack. When an access control decision is made, this stack of protection
domains is examined; only if every protection domain possesses the
necessary permission does the access control check pass. If a protection
domain is examined that does not possess the necessary permission, an
AccessControlException is thrown.
Consider the following two lines of code:
FilePermission perm = new FilePermission("file", "read");
AccessController.checkPermission(perm);
This fragment of code checks whether the calling thread has permission to
read the given file. That is, each class on this thread's execution stack must
belong to a protection domain that includes the requested permission.
Assume that this thread creates a new thread. This new child thread has a
new stack with a new security context. If the parent's security context was not
304
Java 2 Network Security
retained, then security decisions made in the child thread would be based
solely on the child’s security context. This would enable less trusted code (in
the parent) to access protected resources by calling more trusted code (in the
child). To prevent this, Java ensures that a child thread automatically inherits
its parent’s security context. This inheritance continues down a
thread-creation hierarchy, so that whenever a resource access is attempted,
the security context of the executing thread, and those of all its ancestors,
must permit the access.
Typically, each class in a thread’s security context must possess the
requested permission in order for an access check to pass. However, this
top-to-bottom stack crawl can be short-circuited by using the
AccessController.doPrivileged() method to mark a class on the stack as
privileged. Once a privileged frame is encountered, no further protection
domains are examined. This means that code that would otherwise lack
permission to access a resource may do so if it calls (directly or indirectly)
code that possesses the permission and calls doPrivileged(). Note that if the
class that calls doPrivileged() does not possess the requested permission, an
AccessControlException is thrown as usual. The doPrivileged() method
enables trusted code to perform operations on behalf of callers that may or
may not have the necessary permission themselves.
An implementation of the PrivilegedAction interface is passed as an argument
to doPrivileged() to define the operation to be performed with privileges
enabled. This interface is used only for operations that do not throw checked
exceptions; operations that throw checked exceptions must be implemented
as a PrivilegedExceptionAction instead.
The AccessControlContext class in java.security is used to make access
control decisions based on the security context defined by an
AccessControlContext object. An AccessControlContext object is created by
calling AccessController.getContext(), which takes a snapshot of the current
calling context and places it in the AccessControlContext returned. The
AccessControlContext.checkPermission() method makes access control
decisions based upon the encapsulated context, rather than the context of the
current execution thread.
10.1.6 Key Management
The package java.security offers several interfaces and classes to provide
key generation and management.
• The Key interface is the top-level interface for all cryptographic keys and
defines the functionality shared by all keys. All keys have three
characteristics:
Security APIs in Java 2
305
1. An algorithm
This is the key algorithm for that key. The key algorithm is usually an
encryption or asymmetric operation algorithm (such as DSA or RSA).
The name of the algorithm of a key is obtained using the getAlgorithm()
method.
2. An encoded form
This is an external encoded form for the key used when a standard
representation of the key is needed outside the JVM, as when
transmitting the key to some other party. The key is encoded according
to a standard format (such as X.509 or PKCS#8), and is returned using
the getEncoded() method.
3. A format
This is the name of the format of the encoded key. It is returned by the
getFormat() method.
• PrivateKey and PublicKey are interfaces that extend the Key. These
interfaces contain no methods or constants. They merely serve to group
(and provide type safety for) all public and private key interfaces. The
specialized public and private key interfaces, such as the DSAPublicKey
and DSAPrivateKey interfaces in the java.security.interfaces package,
extend PublicKey and PrivateKey respectively.
• The java.security package also contains classes to manage keys and key
pairs, such as the KeyFactory class, which is used to convert keys into key
specifications (and vice versa), and the KeyFactorySpi class, which is
used to define the Service Provider Interface (SPI) for the KeyFactory
class. As we will see in 10.2, “The Package java.security.spec” on page
322, a representation of key material is opaque if it does not give you any
direct access to the key material fields.
Key factories are bi-directional. This means that they allow you to build an
opaque key object from given key material (specification), or to retrieve the
underlying key material of a key object in a suitable format. A KeyFactory
object can be created using the static KeyFactory.getInstance() method.
From a key specification, you can generate the keys using the
generatePublic() and generatePrivate() methods. To get the key
specification from a KeyFactory, you can use the getKeySpec() method.
• The KeyPair class is used to hold a public key and a private key. It
provides getPrivate() and getPublic() methods to get the private and the
public keys respectively.
306
Java 2 Network Security
• The KeyPairGenerator class is used to generate key pairs, while the
KeyPairGeneratorSpi class is used to define the SPI for the
KeyPairGenerator class.
A KeyPairGenerator object can be created using the getInstance() static
method for the KeyPairGenerator class. A KeyPairGenerator object must
be initialized before it can generate keys. To do this, four methods are
provided, all of them called initialize(), but each having a different
signature. The four initialize() methods allow you to:
• Generate a key pair in an algorithm-independent manner or in an
algorithm-specific manner.
• Provide a specific source of randomness or use the SecureRandom
implementation of the highest-priority installed provider as the source
of randomness.
When you initialize a key pair in an algorithm-independent manner, you
specify the key size. If you initialize in an algorithm-specific way, you
supply the AlgorithmParameterSpec to the generator.
In the algorithm-independent case, it is up to the provider to determine the
algorithm-specific parameters to be associated with each of the keys. The
provider may use pre-computed parameter values, or may generate new
values. For example:
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN");
random.setSeed(userSeed);
keyGen.initialize(1024, random);
In the algorithm-specific case, the user supplies the parameters to
initialize a key pair:
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA");
DSAParameterSpec dsaSpec = new DSAParameterSpec(p, q, g);
SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN");
random.setSeed(userSeed);
keyGen.initialize(dsaSpec, random);
• The KeyStore class is used to represent an in-memory collection of keys
and certificates, while the KeyStoreSpi class defines the SPI for the
KeyStore class. You can generate a KeyStore object using the static
getInstance() method. To load a KeyStore object from an InputStream, the
load() method is provided. The store() method can be used to store the
keystore to an OutputStream.
Security APIs in Java 2
307
The KeyStore class provides methods to get and set keys and certificates
from the keystore. For instance, aliases() lists all the alias names in the
keystore, deleteEntry() deletes the entry identified by a specific alias from
the keystore, and getKey() gets the key associated with a given alias from
the keystore.
10.1.6.1 An Example of Keystore Management
The following program loads a KeyStore object, gets a key with alias marco
from it and stores it into another KeyStore object. In other words, this code
performs exactly the same function as the -export and -import commands
associated with the keytool utility (see 9.1, “Key and Certificate Management
Tool” on page 259).
The code comments explain the operations in detail:
class KeyStoreManagement
{
public static void main(String[] args)
{
try
{
// create the Keystore object
KeyStore ks = KeyStore.getInstance("JKS", "SUN");
String keypass = "marcop";
char[] pwd = new char[6];
for (int i = 0; i < pwd.length; i++)
pwd[i] = keypass.charAt(i);
// load the keystore from the system
FileInputStream fisk = new
FileInputStream("D:\\itso\\ch10\\keystore1\\marcostore");
ks.load(fisk, pwd);
// get the certificate from the keystore with alias marco
// similar to keytool -export
X509Certificate certs = (X509Certificate)ks.getCertificate("marco");
// Storing the same certificate in other keystore.
// create the keystore object
KeyStore itsostore = KeyStore.getInstance("JKS", "SUN");
FileInputStream fisk1 = new
FileInputStream("D:\\itso\\ch10\\keystore2\\marcostore");
Figure 130. (Part 1 of 2). KeyStoreManagement.java
308
Java 2 Network Security