In Java 1.1, the primary tool that was used for key management was javakey, which is based heavily on the Identity and IdentityScope classes. The keytool utility that comes with 1.2[1] is a better way to implement key management, and the KeyStore class on which keytool is based is definitely more flexible than the classes on which javakey is based. In addition, the javakey database uses some classes and interfaces that have been deprecated in 1.2--primarily the java.security.Certificate interface.
[1]1.2 is now Java 2.
Nonetheless, for developers who are still using 1.1, a key management system based upon the Identity and IdentityScope classes is the only possible solution. In this appendix, we'll show how these classes can be used for key management. All of the techniques we'll discuss in this appendix have a complementary technique in key management with the KeyStore class. In addition, the Identity and IdentityScope classes have been deprecated in 1.2, so you should really move to the keystore implementation as soon as possible.
You probably noticed in Chapter 10, "Keys and Certificates" that none of the key classes had any notion of whom the key belonged to. Keys are really just an arbitrary-appearing series of bytes. The set of classes we'll examine now deal with the notion of identity: the entity to which a key belongs. An identity can represent an individual or a corporation (or anything else that can possess a public or a private key).
First we'll look at the primary class used to encapsulate an entity that has a public key, the Identity class (java.security.Identity):
Implement an identity--an entity that has a public key. In 1.1, this class is abstract.
An identity object holds only a public key; private keys are held in a different type of object (the signer object, which we'll look at a little later). Hence, identity objects represent the entities in the world who have sent you their public keys in order for you to verify their identity.
An identity contains five pieces of information:
A name--the name of the identity; this satisfies the Principal interface that the identity implements.
A public key.
An optional information string describing the identity.
An optional identity scope. Identities can be aggregated into a collection, which is called an identity scope.
A list of certificates that vouch for the identity.
Note that the default implementation of an identity object carries with it no notion of trustworthiness. You're free to add that feature to your own identity class.
If you want to use an identity object, you have the following methods at your disposal:
Return the name of the identity.
Return the identity scope to which the identity belongs.
Return the public key associated with the identity.
Set the public key associated with the identity to the given public key. This replaces any previous public key as well as any previous certificates associated with this identity. If the public key is already associated with another identity in the identity scope to which this identity belongs, a KeyManagementException is thrown. The implementation of this method in the base class does not actually check the identity scope to see if the key already exists in another identity; it's up to the concrete subclass to provide this functionality.
Return the information string associated with the identity.
Set the information string in the identity, replacing any existing information string.
Add the given certificate to the list of certificates in the identity. If the identity has a public key and that public key does not match the public key in the certificate, a KeyManagementException is thrown. If the identity does not have a public key, the public key in the certificate becomes the public key for the identity. Like the setPublicKey() method, this should generate a KeyManagementException if this conflicts with another key in the identity scope, but the implementation in the base class doesn't automatically provide that.
Remove the given certificate from the list of certificates in the identity. If the given certificate isn't in the identity's list of certificates, no exception is thrown.
Return a copy of the array of certificates held in the identity. The array itself is a copy of what is held by the object, but the certificate objects themselves are not.
Test if the given identity is equal to the current object. Identities are considered equal if they are in the same scope and have the same name. Otherwise, they are considered equal if the identityEquals() method returns true. By default, identities in different scopes are considered equal by the identityEquals() method if they have the same name and the same public key.
There are two ways to obtain an identity object--via the getIdentity() method of the IdentityScope class or by implementing and constructing an instance of your own subclass of the Identity class.
An application that wants to work with identities will typically provide its own identity class. A typical implementation of the Identity class is trivial:
public class XYZIdentity extends Identity { public XYZIdentity(String name) throws KeyManagementException { super(name); } }
Because all of the methods in the Identity class are fully implemented, our class need only construct itself. Here are the constructors in the Identity class that we have the option of calling:
Construct an unnamed identity. This constructor is not designed to be used directly; it is provided for use by object serialization only.
Construct an identity object that does not belong to an identity scope.
Construct an identity object that belongs to the given scope. A KeyManagementException is thrown if the given name already exists in the identity scope.
We've chosen in this example only to implement the second of these constructors.
Other than the constructor, we are not required to implement any methods in our class. If you are implementing an identity within an identity scope, there are methods that you'll need to override in order to get the expected semantics.
Our identity class has one other option available to it, and that is the ability to determine when two identities will compare as equal (via the equals() method). The equals() method itself is final, and it will claim that two identities are equal if they exist in the same scope and have the same name. If either of those tests fails, however, the equals() method relies on the following method to check for equality:
Test for equality between the given identity and this identity. The default behavior for this method is to return true if the identities have the same name and the same key.
If your identity class has other information, you may want to override this method to take that other information into account.
The identity class uses the checkSecurityAccess() method of the security manager to prevent many of its operations from being performed by untrusted classes. Table B-1 lists the methods of the Identity class that make this check and the argument they pass to the checkSecurityAccess() method.
The argument to the checkSecurityAccess() method is constructed from four pieces of information: the name of the class that is providing the implementation of the identity class, the string listed in the table above, the name of the particular identity in question (that is, the string returned by the getName() method), and the name of the class that implements the identity scope to which the identity belongs (if any).
In common implementations of the security manager, this string is ignored and trusted classes are typically able to work with identities, while untrusted classes are not.
An identity has a public key, which can be used to verify the digital signature of something signed by the identity. In order to create a digital signature, we need a private key. An identity that carries with it a private key is modeled by the Signer class (java.security.Signer):
A class to model an entity that has both a public key and a private key. Since this is a subclass of the Identity class, the public key comes from the implementation of that class, and a signer class needs only to be concerned with the private key.
The Signer class is fully implemented even though it is declared as abstract; an implementation of the Signer class need not implement any methods.
A signer is used just like an identity, with these additional methods:
Return the private key of the signer.
Set both the public and private key of the signer. Since public and private keys must match in order to be used, this class requires that in order to set the private key, the public key must be set at the same time. If only one key is present in the key pair, an InvalidParameterException is thrown. The act of setting the public key might generate a KeyManagementException (a subclass of KeyException, which this method throws).
Except for these two operations, a signer is identical to an identity.
Signers are trivial to implement, given that none of their methods are abstract. Hence, it is simply a matter of calling the appropriate constructor:
public class XYZSigner extends Signer { public XYZSigner(String name) throws KeyManagementException { super(name); } }
Note an unfortunate problem here: if you've added additional logic to your identity subclass, your signer subclass cannot use that logic. Your own signer subclass must extend Java's Signer class, not your own identity subclass.
In addition to the security checks that will be made as part of the methods of the Identity class, the signer class calls the checkSecurityAccess() method of the security manager in the following cases with the strings in Table B-2.
Method |
Parameter |
---|---|
Class DefinitiongetPrivateKey() |
Class Definitionget.private.key |
Class DefinitionsetKeyPair() |
Class Definitionset.private.keypair |
As with the Identity class, the actual string passed to the security manager is preceded with the name of the class, and the name of the identity is appended to the class along with the name of the identity's scope.
Copyright © 2001 O'Reilly & Associates. All rights reserved.